mirror of
https://github.com/whekin/household-bot.git
synced 2026-03-31 19:14:03 +00:00
feat(bot): add observable notification management
This commit is contained in:
@@ -63,6 +63,7 @@ describe('createAdHocNotificationJobsHandler', () => {
|
||||
},
|
||||
listUpcomingNotifications: async () => [],
|
||||
cancelNotification: async () => ({ status: 'not_found' }),
|
||||
updateNotification: async () => ({ status: 'not_found' }),
|
||||
listDueNotifications: async () => [dueNotification()],
|
||||
claimDueNotification: async () => true,
|
||||
releaseDueNotification: async () => {},
|
||||
|
||||
@@ -316,6 +316,9 @@ describe('registerAdHocNotifications', () => {
|
||||
async cancelNotification() {
|
||||
return { status: 'not_found' }
|
||||
},
|
||||
async updateNotification() {
|
||||
return { status: 'not_found' }
|
||||
},
|
||||
async listDueNotifications() {
|
||||
return []
|
||||
},
|
||||
@@ -415,6 +418,9 @@ describe('registerAdHocNotifications', () => {
|
||||
async cancelNotification() {
|
||||
return { status: 'not_found' }
|
||||
},
|
||||
async updateNotification() {
|
||||
return { status: 'not_found' }
|
||||
},
|
||||
async listDueNotifications() {
|
||||
return []
|
||||
},
|
||||
@@ -497,6 +503,9 @@ describe('registerAdHocNotifications', () => {
|
||||
async cancelNotification() {
|
||||
return { status: 'not_found' }
|
||||
},
|
||||
async updateNotification() {
|
||||
return { status: 'not_found' }
|
||||
},
|
||||
async listDueNotifications() {
|
||||
return []
|
||||
},
|
||||
|
||||
@@ -217,26 +217,53 @@ export function formatReminderWhen(input: {
|
||||
: formatScheduledFor(input.locale, input.scheduledForIso, input.timezone)
|
||||
}
|
||||
|
||||
function deliveryModeLabel(locale: BotLocale, mode: AdHocNotificationDeliveryMode): string {
|
||||
if (locale === 'ru') {
|
||||
switch (mode) {
|
||||
case 'topic':
|
||||
return 'в этот топик'
|
||||
case 'dm_all':
|
||||
return 'всем в личку'
|
||||
case 'dm_selected':
|
||||
return 'выбранным в личку'
|
||||
function listedNotificationLine(input: {
|
||||
locale: BotLocale
|
||||
timezone: string
|
||||
item: Awaited<ReturnType<AdHocNotificationService['listUpcomingNotifications']>>[number]
|
||||
}): string {
|
||||
const when = formatReminderWhen({
|
||||
locale: input.locale,
|
||||
scheduledForIso: input.item.scheduledFor.toString(),
|
||||
timezone: input.timezone
|
||||
})
|
||||
const details: string[] = []
|
||||
|
||||
if (input.item.assigneeDisplayName) {
|
||||
details.push(
|
||||
input.locale === 'ru'
|
||||
? `для ${input.item.assigneeDisplayName}`
|
||||
: `for ${input.item.assigneeDisplayName}`
|
||||
)
|
||||
}
|
||||
|
||||
if (input.item.deliveryMode !== 'topic') {
|
||||
if (input.item.deliveryMode === 'dm_all') {
|
||||
details.push(input.locale === 'ru' ? 'всем в личку' : 'DM to everyone')
|
||||
} else {
|
||||
const names = input.item.dmRecipientDisplayNames.join(', ')
|
||||
details.push(
|
||||
input.locale === 'ru'
|
||||
? names.length > 0
|
||||
? `в личку: ${names}`
|
||||
: 'в выбранные лички'
|
||||
: names.length > 0
|
||||
? `DM: ${names}`
|
||||
: 'DM selected members'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'topic':
|
||||
return 'this topic'
|
||||
case 'dm_all':
|
||||
return 'DM all members'
|
||||
case 'dm_selected':
|
||||
return 'DM selected members'
|
||||
if (input.item.creatorDisplayName !== input.item.assigneeDisplayName) {
|
||||
details.push(
|
||||
input.locale === 'ru'
|
||||
? `создал ${input.item.creatorDisplayName}`
|
||||
: `created by ${input.item.creatorDisplayName}`
|
||||
)
|
||||
}
|
||||
|
||||
const suffix = details.length > 0 ? `\n${details.join(' · ')}` : ''
|
||||
return `${when}\n${input.item.notificationText}${suffix}`
|
||||
}
|
||||
|
||||
function notificationSummaryText(input: {
|
||||
@@ -635,36 +662,42 @@ export function registerAdHocNotifications(options: {
|
||||
await replyInTopic(
|
||||
ctx,
|
||||
locale === 'ru'
|
||||
? 'Пока нет будущих напоминаний, которые вы можете отменить.'
|
||||
: 'There are no upcoming notifications you can cancel yet.'
|
||||
? 'Пока будущих напоминаний нет.'
|
||||
: 'There are no upcoming notifications yet.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const lines = items.slice(0, 10).map((item, index) => {
|
||||
const when = formatScheduledFor(
|
||||
locale,
|
||||
item.scheduledFor.toString(),
|
||||
reminderContext.timezone
|
||||
)
|
||||
return `${index + 1}. ${item.notificationText}\n${when}\n${deliveryModeLabel(locale, item.deliveryMode)}`
|
||||
})
|
||||
const listedItems = items.slice(0, 10).map((item, index) => ({
|
||||
item,
|
||||
index
|
||||
}))
|
||||
const lines = listedItems.map(
|
||||
({ item, index }) =>
|
||||
`${index + 1}. ${listedNotificationLine({
|
||||
locale,
|
||||
timezone: reminderContext.timezone,
|
||||
item
|
||||
})}`
|
||||
)
|
||||
|
||||
const keyboard: InlineKeyboardMarkup = {
|
||||
inline_keyboard: items.slice(0, 10).map((item, index) => [
|
||||
{
|
||||
text: locale === 'ru' ? `Отменить ${index + 1}` : `Cancel ${index + 1}`,
|
||||
callback_data: `${AD_HOC_NOTIFICATION_CANCEL_SAVED_PREFIX}${item.id}`
|
||||
}
|
||||
])
|
||||
inline_keyboard: listedItems
|
||||
.filter(({ item }) => item.canCancel)
|
||||
.map(({ item, index }) => [
|
||||
{
|
||||
text: locale === 'ru' ? `Отменить ${index + 1}` : `Cancel ${index + 1}`,
|
||||
callback_data: `${AD_HOC_NOTIFICATION_CANCEL_SAVED_PREFIX}${item.id}`
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
await replyInTopic(
|
||||
ctx,
|
||||
[locale === 'ru' ? 'Ближайшие напоминания:' : 'Upcoming notifications:', '', ...lines].join(
|
||||
'\n'
|
||||
'\n\n'
|
||||
),
|
||||
keyboard
|
||||
keyboard.inline_keyboard.length > 0 ? keyboard : undefined
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -73,6 +73,10 @@ import {
|
||||
createMiniAppUpdateUtilityBillHandler
|
||||
} from './miniapp-billing'
|
||||
import { createMiniAppLocalePreferenceHandler } from './miniapp-locale'
|
||||
import {
|
||||
createMiniAppCancelNotificationHandler,
|
||||
createMiniAppUpdateNotificationHandler
|
||||
} from './miniapp-notifications'
|
||||
import { createNbgExchangeRateProvider } from './nbg-exchange-rates'
|
||||
import { createOpenAiChatAssistant } from './openai-chat-assistant'
|
||||
import { createOpenAiAdHocNotificationInterpreter } from './openai-ad-hoc-notification-interpreter'
|
||||
@@ -635,10 +639,31 @@ export async function createBotRuntimeApp(): Promise<BotRuntimeApp> {
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
financeServiceForHousehold,
|
||||
adHocNotificationService: adHocNotificationService!,
|
||||
onboardingService: householdOnboardingService,
|
||||
logger: getLogger('miniapp-dashboard')
|
||||
})
|
||||
: undefined,
|
||||
miniAppUpdateNotification:
|
||||
householdOnboardingService && adHocNotificationService
|
||||
? createMiniAppUpdateNotificationHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
adHocNotificationService,
|
||||
logger: getLogger('miniapp-notifications')
|
||||
})
|
||||
: undefined,
|
||||
miniAppCancelNotification:
|
||||
householdOnboardingService && adHocNotificationService
|
||||
? createMiniAppCancelNotificationHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
botToken: runtime.telegramBotToken,
|
||||
onboardingService: householdOnboardingService,
|
||||
adHocNotificationService,
|
||||
logger: getLogger('miniapp-notifications')
|
||||
})
|
||||
: undefined,
|
||||
miniAppPendingMembers: householdOnboardingService
|
||||
? createMiniAppPendingMembersHandler({
|
||||
allowedOrigins: runtime.miniAppAllowedOrigins,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import {
|
||||
type AdHocNotificationService,
|
||||
createFinanceCommandService,
|
||||
createHouseholdOnboardingService
|
||||
} from '@household/application'
|
||||
@@ -314,6 +315,23 @@ function onboardingRepository(): HouseholdConfigurationRepository {
|
||||
}
|
||||
}
|
||||
|
||||
function notificationService(
|
||||
items: Awaited<ReturnType<AdHocNotificationService['listUpcomingNotifications']>> = []
|
||||
): AdHocNotificationService {
|
||||
return {
|
||||
scheduleNotification: async () => {
|
||||
throw new Error('not implemented')
|
||||
},
|
||||
listUpcomingNotifications: async () => items,
|
||||
cancelNotification: async () => ({ status: 'not_found' }),
|
||||
updateNotification: async () => ({ status: 'not_found' }),
|
||||
listDueNotifications: async () => [],
|
||||
claimDueNotification: async () => false,
|
||||
releaseDueNotification: async () => {},
|
||||
markNotificationSent: async () => null
|
||||
}
|
||||
}
|
||||
|
||||
describe('createMiniAppDashboardHandler', () => {
|
||||
test('returns a dashboard for an authenticated household member', async () => {
|
||||
const authDate = Math.floor(Date.now() / 1000)
|
||||
@@ -344,10 +362,28 @@ describe('createMiniAppDashboardHandler', () => {
|
||||
isAdmin: true
|
||||
}
|
||||
]
|
||||
const adHocNotificationService = notificationService([
|
||||
{
|
||||
id: 'notification-1',
|
||||
creatorMemberId: 'member-1',
|
||||
creatorDisplayName: 'Stan',
|
||||
assigneeMemberId: null,
|
||||
assigneeDisplayName: null,
|
||||
notificationText: 'Stan, breakfast time.',
|
||||
scheduledFor: instantFromIso('2026-03-25T06:00:00.000Z'),
|
||||
deliveryMode: 'topic',
|
||||
dmRecipientMemberIds: [],
|
||||
dmRecipientDisplayNames: [],
|
||||
status: 'scheduled',
|
||||
canCancel: true,
|
||||
canEdit: true
|
||||
}
|
||||
])
|
||||
const dashboard = createMiniAppDashboardHandler({
|
||||
allowedOrigins: ['http://localhost:5173'],
|
||||
botToken: 'test-bot-token',
|
||||
financeServiceForHousehold: () => financeService,
|
||||
adHocNotificationService,
|
||||
onboardingService: createHouseholdOnboardingService({
|
||||
repository: householdRepository
|
||||
})
|
||||
@@ -415,6 +451,17 @@ describe('createMiniAppDashboardHandler', () => {
|
||||
currency: 'GEL',
|
||||
displayCurrency: 'GEL'
|
||||
}
|
||||
],
|
||||
notifications: [
|
||||
{
|
||||
id: 'notification-1',
|
||||
summaryText: 'Stan, breakfast time.',
|
||||
deliveryMode: 'topic',
|
||||
dmRecipientMemberIds: [],
|
||||
creatorDisplayName: 'Stan',
|
||||
canCancel: true,
|
||||
canEdit: true
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
@@ -550,6 +597,7 @@ describe('createMiniAppDashboardHandler', () => {
|
||||
allowedOrigins: ['http://localhost:5173'],
|
||||
botToken: 'test-bot-token',
|
||||
financeServiceForHousehold: () => financeService,
|
||||
adHocNotificationService: notificationService(),
|
||||
onboardingService: createHouseholdOnboardingService({
|
||||
repository: householdRepository
|
||||
})
|
||||
@@ -644,6 +692,7 @@ describe('createMiniAppDashboardHandler', () => {
|
||||
allowedOrigins: ['http://localhost:5173'],
|
||||
botToken: 'test-bot-token',
|
||||
financeServiceForHousehold: () => financeService,
|
||||
adHocNotificationService: notificationService(),
|
||||
onboardingService: createHouseholdOnboardingService({
|
||||
repository: householdRepository
|
||||
})
|
||||
@@ -706,6 +755,7 @@ describe('createMiniAppDashboardHandler', () => {
|
||||
allowedOrigins: ['http://localhost:5173'],
|
||||
botToken: 'test-bot-token',
|
||||
financeServiceForHousehold: () => financeService,
|
||||
adHocNotificationService: notificationService(),
|
||||
onboardingService: createHouseholdOnboardingService({
|
||||
repository: householdRepository
|
||||
})
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import type { FinanceCommandService, HouseholdOnboardingService } from '@household/application'
|
||||
import type {
|
||||
AdHocNotificationService,
|
||||
FinanceCommandService,
|
||||
HouseholdOnboardingService
|
||||
} from '@household/application'
|
||||
import { Money } from '@household/domain'
|
||||
import type { Logger } from '@household/observability'
|
||||
|
||||
@@ -14,6 +18,7 @@ export function createMiniAppDashboardHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
adHocNotificationService: AdHocNotificationService
|
||||
onboardingService: HouseholdOnboardingService
|
||||
logger?: Logger
|
||||
}): {
|
||||
@@ -74,6 +79,10 @@ export function createMiniAppDashboardHandler(options: {
|
||||
const dashboard = await options
|
||||
.financeServiceForHousehold(session.member.householdId)
|
||||
.generateDashboard()
|
||||
const notifications = await options.adHocNotificationService.listUpcomingNotifications({
|
||||
householdId: session.member.householdId,
|
||||
viewerMemberId: session.member.id
|
||||
})
|
||||
if (!dashboard) {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'No billing cycle available' },
|
||||
@@ -178,6 +187,21 @@ export function createMiniAppDashboardHandler(options: {
|
||||
})) ?? []
|
||||
}
|
||||
: {})
|
||||
})),
|
||||
notifications: notifications.map((notification) => ({
|
||||
id: notification.id,
|
||||
summaryText: notification.notificationText,
|
||||
scheduledFor: notification.scheduledFor.toString(),
|
||||
status: notification.status,
|
||||
deliveryMode: notification.deliveryMode,
|
||||
dmRecipientMemberIds: notification.dmRecipientMemberIds,
|
||||
dmRecipientDisplayNames: notification.dmRecipientDisplayNames,
|
||||
creatorMemberId: notification.creatorMemberId,
|
||||
creatorDisplayName: notification.creatorDisplayName,
|
||||
assigneeMemberId: notification.assigneeMemberId,
|
||||
assigneeDisplayName: notification.assigneeDisplayName,
|
||||
canCancel: notification.canCancel,
|
||||
canEdit: notification.canEdit
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
216
apps/bot/src/miniapp-notifications.ts
Normal file
216
apps/bot/src/miniapp-notifications.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import type { AdHocNotificationService, HouseholdOnboardingService } from '@household/application'
|
||||
import { Temporal } from '@household/domain'
|
||||
import type { Logger } from '@household/observability'
|
||||
import type { AdHocNotificationDeliveryMode } from '@household/ports'
|
||||
|
||||
import {
|
||||
allowedMiniAppOrigin,
|
||||
createMiniAppSessionService,
|
||||
miniAppErrorResponse,
|
||||
miniAppJsonResponse,
|
||||
readMiniAppRequestPayload,
|
||||
type MiniAppSessionResult
|
||||
} from './miniapp-auth'
|
||||
|
||||
async function authenticateMemberSession(
|
||||
request: Request,
|
||||
sessionService: ReturnType<typeof createMiniAppSessionService>,
|
||||
origin: string | undefined
|
||||
): Promise<
|
||||
| Response
|
||||
| {
|
||||
member: NonNullable<MiniAppSessionResult['member']>
|
||||
}
|
||||
> {
|
||||
const payload = await readMiniAppRequestPayload(request)
|
||||
if (!payload.initData) {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing initData' }, 400, origin)
|
||||
}
|
||||
|
||||
const session = await sessionService.authenticate(payload)
|
||||
if (!session) {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Invalid Telegram init data' }, 401, origin)
|
||||
}
|
||||
|
||||
if (!session.authorized || !session.member || session.member.status !== 'active') {
|
||||
return miniAppJsonResponse(
|
||||
{ ok: false, error: 'Access limited to active household members' },
|
||||
403,
|
||||
origin
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
member: session.member
|
||||
}
|
||||
}
|
||||
|
||||
async function parseJsonBody<T>(request: Request): Promise<T> {
|
||||
const text = await request.clone().text()
|
||||
if (text.trim().length === 0) {
|
||||
return {} as T
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(text) as T
|
||||
} catch {
|
||||
throw new Error('Invalid JSON body')
|
||||
}
|
||||
}
|
||||
|
||||
function parseScheduledLocal(localValue: string, timezone: string): Temporal.Instant {
|
||||
return Temporal.ZonedDateTime.from(`${localValue}[${timezone}]`).toInstant()
|
||||
}
|
||||
|
||||
function isDeliveryMode(value: string | undefined): value is AdHocNotificationDeliveryMode {
|
||||
return value === 'topic' || value === 'dm_all' || value === 'dm_selected'
|
||||
}
|
||||
|
||||
export function createMiniAppUpdateNotificationHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
adHocNotificationService: AdHocNotificationService
|
||||
logger?: Logger
|
||||
}): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
const origin = allowedMiniAppOrigin(request, options.allowedOrigins)
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return miniAppJsonResponse({ ok: true }, 204, origin)
|
||||
}
|
||||
|
||||
if (request.method !== 'POST') {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Method Not Allowed' }, 405, origin)
|
||||
}
|
||||
|
||||
try {
|
||||
const auth = await authenticateMemberSession(request, sessionService, origin)
|
||||
if (auth instanceof Response) {
|
||||
return auth
|
||||
}
|
||||
|
||||
const parsed = await parseJsonBody<{
|
||||
notificationId?: string
|
||||
scheduledLocal?: string
|
||||
timezone?: string
|
||||
deliveryMode?: string
|
||||
dmRecipientMemberIds?: string[]
|
||||
}>(request)
|
||||
|
||||
const notificationId = parsed.notificationId?.trim()
|
||||
if (!notificationId) {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing notificationId' }, 400, origin)
|
||||
}
|
||||
|
||||
const scheduledLocal = parsed.scheduledLocal?.trim()
|
||||
const timezone = parsed.timezone?.trim()
|
||||
const deliveryMode = parsed.deliveryMode?.trim()
|
||||
|
||||
const result = await options.adHocNotificationService.updateNotification({
|
||||
notificationId,
|
||||
viewerMemberId: auth.member.id,
|
||||
...(scheduledLocal && timezone
|
||||
? {
|
||||
scheduledFor: parseScheduledLocal(scheduledLocal, timezone),
|
||||
timePrecision: 'exact' as const
|
||||
}
|
||||
: {}),
|
||||
...(deliveryMode && isDeliveryMode(deliveryMode)
|
||||
? {
|
||||
deliveryMode,
|
||||
dmRecipientMemberIds: parsed.dmRecipientMemberIds ?? []
|
||||
}
|
||||
: {})
|
||||
})
|
||||
|
||||
switch (result.status) {
|
||||
case 'updated':
|
||||
return miniAppJsonResponse({ ok: true, authorized: true }, 200, origin)
|
||||
case 'invalid':
|
||||
return miniAppJsonResponse({ ok: false, error: result.reason }, 400, origin)
|
||||
case 'forbidden':
|
||||
return miniAppJsonResponse({ ok: false, error: 'Forbidden' }, 403, origin)
|
||||
case 'not_found':
|
||||
return miniAppJsonResponse({ ok: false, error: 'Notification not found' }, 404, origin)
|
||||
case 'already_handled':
|
||||
case 'past_due':
|
||||
return miniAppJsonResponse({ ok: false, error: result.status }, 409, origin)
|
||||
}
|
||||
} catch (error) {
|
||||
return miniAppErrorResponse(error, origin, options.logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createMiniAppCancelNotificationHandler(options: {
|
||||
allowedOrigins: readonly string[]
|
||||
botToken: string
|
||||
onboardingService: HouseholdOnboardingService
|
||||
adHocNotificationService: AdHocNotificationService
|
||||
logger?: Logger
|
||||
}): {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
} {
|
||||
const sessionService = createMiniAppSessionService({
|
||||
botToken: options.botToken,
|
||||
onboardingService: options.onboardingService
|
||||
})
|
||||
|
||||
return {
|
||||
handler: async (request) => {
|
||||
const origin = allowedMiniAppOrigin(request, options.allowedOrigins)
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return miniAppJsonResponse({ ok: true }, 204, origin)
|
||||
}
|
||||
|
||||
if (request.method !== 'POST') {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Method Not Allowed' }, 405, origin)
|
||||
}
|
||||
|
||||
try {
|
||||
const auth = await authenticateMemberSession(request, sessionService, origin)
|
||||
if (auth instanceof Response) {
|
||||
return auth
|
||||
}
|
||||
|
||||
const parsed = await parseJsonBody<{
|
||||
notificationId?: string
|
||||
}>(request)
|
||||
const notificationId = parsed.notificationId?.trim()
|
||||
if (!notificationId) {
|
||||
return miniAppJsonResponse({ ok: false, error: 'Missing notificationId' }, 400, origin)
|
||||
}
|
||||
|
||||
const result = await options.adHocNotificationService.cancelNotification({
|
||||
notificationId,
|
||||
viewerMemberId: auth.member.id
|
||||
})
|
||||
|
||||
switch (result.status) {
|
||||
case 'cancelled':
|
||||
return miniAppJsonResponse({ ok: true, authorized: true }, 200, origin)
|
||||
case 'forbidden':
|
||||
return miniAppJsonResponse({ ok: false, error: 'Forbidden' }, 403, origin)
|
||||
case 'not_found':
|
||||
return miniAppJsonResponse({ ok: false, error: 'Notification not found' }, 404, origin)
|
||||
case 'already_handled':
|
||||
case 'past_due':
|
||||
return miniAppJsonResponse({ ok: false, error: result.status }, 409, origin)
|
||||
}
|
||||
} catch (error) {
|
||||
return miniAppErrorResponse(error, origin, options.logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,18 @@ export interface BotWebhookServerOptions {
|
||||
handler: (request: Request) => Promise<Response>
|
||||
}
|
||||
| undefined
|
||||
miniAppUpdateNotification?:
|
||||
| {
|
||||
path?: string
|
||||
handler: (request: Request) => Promise<Response>
|
||||
}
|
||||
| undefined
|
||||
miniAppCancelNotification?:
|
||||
| {
|
||||
path?: string
|
||||
handler: (request: Request) => Promise<Response>
|
||||
}
|
||||
| undefined
|
||||
miniAppJoin?:
|
||||
| {
|
||||
path?: string
|
||||
@@ -226,6 +238,10 @@ export function createBotWebhookServer(options: BotWebhookServerOptions): {
|
||||
: `/${options.webhookPath}`
|
||||
const miniAppAuthPath = options.miniAppAuth?.path ?? '/api/miniapp/session'
|
||||
const miniAppDashboardPath = options.miniAppDashboard?.path ?? '/api/miniapp/dashboard'
|
||||
const miniAppUpdateNotificationPath =
|
||||
options.miniAppUpdateNotification?.path ?? '/api/miniapp/notifications/update'
|
||||
const miniAppCancelNotificationPath =
|
||||
options.miniAppCancelNotification?.path ?? '/api/miniapp/notifications/cancel'
|
||||
const miniAppJoinPath = options.miniAppJoin?.path ?? '/api/miniapp/join'
|
||||
const miniAppPendingMembersPath =
|
||||
options.miniAppPendingMembers?.path ?? '/api/miniapp/admin/pending-members'
|
||||
@@ -301,6 +317,14 @@ export function createBotWebhookServer(options: BotWebhookServerOptions): {
|
||||
return await options.miniAppDashboard.handler(request)
|
||||
}
|
||||
|
||||
if (options.miniAppUpdateNotification && url.pathname === miniAppUpdateNotificationPath) {
|
||||
return await options.miniAppUpdateNotification.handler(request)
|
||||
}
|
||||
|
||||
if (options.miniAppCancelNotification && url.pathname === miniAppCancelNotificationPath) {
|
||||
return await options.miniAppCancelNotification.handler(request)
|
||||
}
|
||||
|
||||
if (options.miniAppJoin && url.pathname === miniAppJoinPath) {
|
||||
return await options.miniAppJoin.handler(request)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user