feat(miniapp): add household general settings

This commit is contained in:
2026-03-12 11:30:11 +04:00
parent 8160f644cc
commit 4c19ee798d
14 changed files with 268 additions and 43 deletions

View File

@@ -1320,6 +1320,35 @@ export function createDbHouseholdConfigurationRepository(databaseUrl: string): {
}
},
async updateHouseholdName(householdId, householdName) {
const updatedHouseholds = await db
.update(schema.households)
.set({
name: householdName
})
.where(eq(schema.households.id, householdId))
.returning({
id: schema.households.id,
name: schema.households.name,
defaultLocale: schema.households.defaultLocale
})
const household = updatedHouseholds[0]
if (!household) {
throw new Error('Failed to update household name')
}
const chat = await this.getHouseholdChatByHouseholdId(householdId)
if (!chat) {
throw new Error('Failed to resolve household chat after name update')
}
return {
...chat,
householdName: household.name
}
},
async updateMemberPreferredLocale(householdId, telegramUserId, locale) {
const rows = await db
.update(schema.members)

View File

@@ -338,6 +338,7 @@ describe('createHouseholdOnboardingService', () => {
member: {
id: 'member-42',
householdId: 'household-1',
householdName: 'Kojori House',
displayName: 'Stan',
status: 'active',
preferredLocale: null,

View File

@@ -16,6 +16,7 @@ export type HouseholdMiniAppAccess =
member: {
id: string
householdId: string
householdName: string
displayName: string
status: HouseholdMemberRecord['status']
isAdmin: boolean
@@ -68,6 +69,7 @@ export interface HouseholdOnboardingService {
member: {
id: string
householdId: string
householdName: string
displayName: string
status: HouseholdMemberRecord['status']
isAdmin: boolean
@@ -161,9 +163,19 @@ export function createHouseholdOnboardingService(options: {
) ?? null)
if (matchingActiveMember) {
const household = await options.repository.getHouseholdChatByHouseholdId(
matchingActiveMember.householdId
)
if (!household) {
throw new Error('Failed to resolve household for active mini app member')
}
return {
status: 'active',
member: toMember(matchingActiveMember)
member: {
...toMember(matchingActiveMember),
householdName: household.householdName
}
}
}
@@ -232,9 +244,19 @@ export function createHouseholdOnboardingService(options: {
).find((member) => member.householdId === household.householdId)
if (activeMember) {
const householdRecord = await options.repository.getHouseholdChatByHouseholdId(
activeMember.householdId
)
if (!householdRecord) {
throw new Error('Failed to resolve household after mini app join')
}
return {
status: 'active',
member: toMember(activeMember)
member: {
...toMember(activeMember),
householdName: householdRecord.householdName
}
}
}

View File

@@ -25,7 +25,14 @@ function repository(): HouseholdConfigurationRepository {
}
}),
getTelegramHouseholdChat: async () => null,
getHouseholdChatByHouseholdId: async () => null,
getHouseholdChatByHouseholdId: async () => ({
householdId: 'household-1',
householdName: 'Kojori House',
telegramChatId: '-100123',
telegramChatType: 'supergroup',
title: 'Kojori House',
defaultLocale: 'ru'
}),
bindHouseholdTopic: async (input) => ({
householdId: input.householdId,
role: input.role,
@@ -268,6 +275,7 @@ describe('createMiniAppAdminService', () => {
expect(result).toEqual({
status: 'ok',
householdName: 'Kojori House',
settings: {
householdId: 'household-1',
settlementCurrency: 'GEL',
@@ -327,6 +335,7 @@ describe('createMiniAppAdminService', () => {
expect(result).toEqual({
status: 'ok',
householdName: 'Kojori House',
settings: {
householdId: 'household-1',
settlementCurrency: 'GEL',

View File

@@ -29,6 +29,7 @@ export interface MiniAppAdminService {
getSettings(input: { householdId: string; actorIsAdmin: boolean }): Promise<
| {
status: 'ok'
householdName: string
settings: HouseholdBillingSettingsRecord
assistantConfig: HouseholdAssistantConfigRecord
categories: readonly HouseholdUtilityCategoryRecord[]
@@ -44,6 +45,7 @@ export interface MiniAppAdminService {
updateSettings(input: {
householdId: string
actorIsAdmin: boolean
householdName?: string
settlementCurrency?: string
paymentBalanceAdjustmentPolicy?: string
rentAmountMajor?: string
@@ -58,6 +60,7 @@ export interface MiniAppAdminService {
}): Promise<
| {
status: 'ok'
householdName: string
settings: HouseholdBillingSettingsRecord
assistantConfig: HouseholdAssistantConfigRecord
}
@@ -215,6 +218,19 @@ function normalizeDisplayName(raw: string): string | null {
return trimmed.replace(/\s+/g, ' ')
}
function normalizeHouseholdName(raw: string | undefined): string | null | undefined {
if (raw === undefined) {
return undefined
}
const trimmed = raw.trim()
if (trimmed.length < 2 || trimmed.length > 120) {
return null
}
return trimmed.replace(/\s+/g, ' ')
}
function defaultAssistantConfig(householdId: string): HouseholdAssistantConfigRecord {
return {
householdId,
@@ -255,6 +271,11 @@ export function createMiniAppAdminService(
}
}
const household = await repository.getHouseholdChatByHouseholdId(input.householdId)
if (!household) {
throw new Error('Failed to resolve household chat for mini app settings')
}
const [settings, assistantConfig, categories, members, memberAbsencePolicies, topics] =
await Promise.all([
repository.getHouseholdBillingSettings(input.householdId),
@@ -269,6 +290,7 @@ export function createMiniAppAdminService(
return {
status: 'ok',
householdName: household.householdName,
settings,
assistantConfig,
categories,
@@ -303,8 +325,11 @@ export function createMiniAppAdminService(
const assistantContext = normalizeAssistantText(input.assistantContext, 1200)
const assistantTone = normalizeAssistantText(input.assistantTone, 160)
const householdName = normalizeHouseholdName(input.householdName)
const nextHouseholdName = householdName ?? undefined
if (
(input.householdName !== undefined && householdName === null) ||
(input.assistantContext !== undefined &&
assistantContext === null &&
input.assistantContext.trim().length > 0) ||
@@ -349,7 +374,7 @@ export function createMiniAppAdminService(
const shouldUpdateAssistantConfig =
assistantContext !== undefined || assistantTone !== undefined
const [settings, nextAssistantConfig] = await Promise.all([
const [settings, nextAssistantConfig, household] = await Promise.all([
repository.updateHouseholdBillingSettings({
householdId: input.householdId,
...(settlementCurrency
@@ -398,11 +423,19 @@ export function createMiniAppAdminService(
householdId: input.householdId,
assistantContext: assistantContext ?? null,
assistantTone: assistantTone ?? null
})
}),
nextHouseholdName !== undefined && repository.updateHouseholdName
? repository.updateHouseholdName(input.householdId, nextHouseholdName)
: repository.getHouseholdChatByHouseholdId(input.householdId)
])
if (!household) {
throw new Error('Failed to resolve household chat after settings update')
}
return {
status: 'ok',
householdName: household.householdName,
settings,
assistantConfig: nextAssistantConfig
}

View File

@@ -213,6 +213,10 @@ export interface HouseholdConfigurationRepository {
householdId: string,
locale: SupportedLocale
): Promise<HouseholdTelegramChatRecord>
updateHouseholdName?(
householdId: string,
householdName: string
): Promise<HouseholdTelegramChatRecord>
updateMemberPreferredLocale(
householdId: string,
telegramUserId: string,