mirror of
https://github.com/whekin/household-bot.git
synced 2026-04-01 06:04:02 +00:00
feat(bot): cut over multi-household member flows
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { FinanceCommandService } from '@household/application'
|
||||
import type { HouseholdConfigurationRepository } from '@household/ports'
|
||||
import type { Bot, Context } from 'grammy'
|
||||
|
||||
function commandArgs(ctx: Context): string[] {
|
||||
@@ -10,9 +11,39 @@ function commandArgs(ctx: Context): string[] {
|
||||
return raw.split(/\s+/).filter(Boolean)
|
||||
}
|
||||
|
||||
export function createFinanceCommandsService(financeService: FinanceCommandService): {
|
||||
function isGroupChat(ctx: Context): boolean {
|
||||
return ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup'
|
||||
}
|
||||
|
||||
export function createFinanceCommandsService(options: {
|
||||
householdConfigurationRepository: HouseholdConfigurationRepository
|
||||
financeServiceForHousehold: (householdId: string) => FinanceCommandService
|
||||
}): {
|
||||
register: (bot: Bot) => void
|
||||
} {
|
||||
async function resolveGroupFinanceService(ctx: Context): Promise<{
|
||||
service: FinanceCommandService
|
||||
householdId: string
|
||||
} | null> {
|
||||
if (!isGroupChat(ctx)) {
|
||||
await ctx.reply('Use this command inside a household group.')
|
||||
return null
|
||||
}
|
||||
|
||||
const household = await options.householdConfigurationRepository.getTelegramHouseholdChat(
|
||||
ctx.chat!.id.toString()
|
||||
)
|
||||
if (!household) {
|
||||
await ctx.reply('Household is not configured for this chat yet. Run /setup first.')
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
service: options.financeServiceForHousehold(household.householdId),
|
||||
householdId: household.householdId
|
||||
}
|
||||
}
|
||||
|
||||
async function requireMember(ctx: Context) {
|
||||
const telegramUserId = ctx.from?.id?.toString()
|
||||
if (!telegramUserId) {
|
||||
@@ -20,33 +51,42 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
return null
|
||||
}
|
||||
|
||||
const member = await financeService.getMemberByTelegramUserId(telegramUserId)
|
||||
const scoped = await resolveGroupFinanceService(ctx)
|
||||
if (!scoped) {
|
||||
return null
|
||||
}
|
||||
|
||||
const member = await scoped.service.getMemberByTelegramUserId(telegramUserId)
|
||||
if (!member) {
|
||||
await ctx.reply('You are not a member of this household.')
|
||||
return null
|
||||
}
|
||||
|
||||
return member
|
||||
return {
|
||||
member,
|
||||
service: scoped.service,
|
||||
householdId: scoped.householdId
|
||||
}
|
||||
}
|
||||
|
||||
async function requireAdmin(ctx: Context) {
|
||||
const member = await requireMember(ctx)
|
||||
if (!member) {
|
||||
const resolved = await requireMember(ctx)
|
||||
if (!resolved) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!member.isAdmin) {
|
||||
if (!resolved.member.isAdmin) {
|
||||
await ctx.reply('Only household admins can use this command.')
|
||||
return null
|
||||
}
|
||||
|
||||
return member
|
||||
return resolved
|
||||
}
|
||||
|
||||
function register(bot: Bot): void {
|
||||
bot.command('cycle_open', async (ctx) => {
|
||||
const admin = await requireAdmin(ctx)
|
||||
if (!admin) {
|
||||
const resolved = await requireAdmin(ctx)
|
||||
if (!resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -57,7 +97,7 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
}
|
||||
|
||||
try {
|
||||
const cycle = await financeService.openCycle(args[0]!, args[1])
|
||||
const cycle = await resolved.service.openCycle(args[0]!, args[1])
|
||||
await ctx.reply(`Cycle opened: ${cycle.period} (${cycle.currency})`)
|
||||
} catch (error) {
|
||||
await ctx.reply(`Failed to open cycle: ${(error as Error).message}`)
|
||||
@@ -65,13 +105,13 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
})
|
||||
|
||||
bot.command('cycle_close', async (ctx) => {
|
||||
const admin = await requireAdmin(ctx)
|
||||
if (!admin) {
|
||||
const resolved = await requireAdmin(ctx)
|
||||
if (!resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const cycle = await financeService.closeCycle(commandArgs(ctx)[0])
|
||||
const cycle = await resolved.service.closeCycle(commandArgs(ctx)[0])
|
||||
if (!cycle) {
|
||||
await ctx.reply('No cycle found to close.')
|
||||
return
|
||||
@@ -84,8 +124,8 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
})
|
||||
|
||||
bot.command('rent_set', async (ctx) => {
|
||||
const admin = await requireAdmin(ctx)
|
||||
if (!admin) {
|
||||
const resolved = await requireAdmin(ctx)
|
||||
if (!resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -96,7 +136,7 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await financeService.setRent(args[0]!, args[1], args[2])
|
||||
const result = await resolved.service.setRent(args[0]!, args[1], args[2])
|
||||
if (!result) {
|
||||
await ctx.reply('No period provided and no open cycle found.')
|
||||
return
|
||||
@@ -111,8 +151,8 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
})
|
||||
|
||||
bot.command('utility_add', async (ctx) => {
|
||||
const admin = await requireAdmin(ctx)
|
||||
if (!admin) {
|
||||
const resolved = await requireAdmin(ctx)
|
||||
if (!resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -123,7 +163,12 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await financeService.addUtilityBill(args[0]!, args[1]!, admin.id, args[2])
|
||||
const result = await resolved.service.addUtilityBill(
|
||||
args[0]!,
|
||||
args[1]!,
|
||||
resolved.member.id,
|
||||
args[2]
|
||||
)
|
||||
if (!result) {
|
||||
await ctx.reply('No open cycle found. Use /cycle_open first.')
|
||||
return
|
||||
@@ -138,13 +183,13 @@ export function createFinanceCommandsService(financeService: FinanceCommandServi
|
||||
})
|
||||
|
||||
bot.command('statement', async (ctx) => {
|
||||
const member = await requireMember(ctx)
|
||||
if (!member) {
|
||||
const resolved = await requireMember(ctx)
|
||||
if (!resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const statement = await financeService.generateStatement(commandArgs(ctx)[0])
|
||||
const statement = await resolved.service.generateStatement(commandArgs(ctx)[0])
|
||||
if (!statement) {
|
||||
await ctx.reply('No cycle found for statement.')
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user