feat: 支持管理员消息推送

This commit is contained in:
richarjiang
2026-04-12 22:18:34 +08:00
parent c60821c5ff
commit 6cee28bf66
26 changed files with 780 additions and 161 deletions

View File

@@ -13,6 +13,7 @@ import type {
SubscriptionMessageConsentSummary,
SubscriptionMessageRequestItem,
SubscriptionMessageRequestResult,
SubscriptionMessageTemplate,
SubscriptionMessageTemplateConfig,
} from '@mp-pilates/shared'
import { ConfigService } from '@nestjs/config'
@@ -20,6 +21,7 @@ import { PrismaService } from '../prisma/prisma.service'
import { UpdateUserMembershipDto } from './dto/update-user-membership.dto'
const VALID_CARD_TYPES = new Set<string>(Object.values(CardTypeCategory))
const ADMIN_BOOKING_SUBSCRIPTION_INCREMENT = 1
type SubscriptionMessageConsentDelegate = PrismaService['subscriptionMessageConsent']
type SubscriptionMessageConsentRecord = Awaited<ReturnType<SubscriptionMessageConsentDelegate['findMany']>>[number]
@@ -32,14 +34,46 @@ export class UserService {
) {}
private buildSubscriptionTemplateConfig(): SubscriptionMessageTemplateConfig {
const templates = [
{
templateId: this.configService.get<string>('WX_SUBSCRIBE_TEMPLATE_BOOKING_CONFIRMED', ''),
scene: SubscriptionMessageScene.BOOKING_CREATED,
description: '购卡或预约时请求一次订阅,用于后续预约确认通知推送',
usageTarget: 'consent' as const,
},
{
templateId: this.configService.get<string>('WX_SUBSCRIBE_TEMPLATE_BOOKING_CONFIRMED', ''),
scene: SubscriptionMessageScene.ADMIN_BOOKING_CREATED,
description: '管理员主动增加预约提醒次数,用于接收学员新预约通知',
usageTarget: 'counter' as const,
},
] satisfies SubscriptionMessageTemplate[]
return {
templates: [
{
templateId: this.configService.get<string>('WX_SUBSCRIBE_TEMPLATE_BOOKING_CONFIRMED', ''),
scene: SubscriptionMessageScene.BOOKING_CREATED,
description: '购卡或预约时请求一次订阅,用于后续预约确认通知推送',
},
].filter((item) => item.templateId),
templates: templates.filter((item) => item.templateId),
}
}
private mapProfile(user: {
id: string
phone: string | null
nickname: string
avatarUrl: string | null
role: string
adminBookingSubscriptionCount: number
createdAt: Date
_count: { memberships: number }
}): UserProfileResponse {
return {
id: user.id,
phone: user.phone,
nickname: user.nickname,
avatarUrl: user.avatarUrl,
role: user.role as UserRole,
activeMembershipCount: user._count.memberships,
adminBookingSubscriptionCount: user.adminBookingSubscriptionCount,
subscriptionMessageTemplates: this.buildSubscriptionTemplateConfig(),
createdAt: user.createdAt.toISOString(),
}
}
@@ -61,16 +95,7 @@ export class UserService {
throw new NotFoundException('User not found')
}
return {
id: user.id,
phone: user.phone,
nickname: user.nickname,
avatarUrl: user.avatarUrl,
role: user.role as UserRole,
activeMembershipCount: user._count.memberships,
subscriptionMessageTemplates: this.buildSubscriptionTemplateConfig(),
createdAt: user.createdAt.toISOString(),
}
return this.mapProfile(user)
}
async updateProfile(
@@ -94,16 +119,7 @@ export class UserService {
},
})
return {
id: updated.id,
phone: updated.phone,
nickname: updated.nickname,
avatarUrl: updated.avatarUrl,
role: updated.role as UserRole,
activeMembershipCount: updated._count.memberships,
subscriptionMessageTemplates: this.buildSubscriptionTemplateConfig(),
createdAt: updated.createdAt.toISOString(),
}
return this.mapProfile(updated)
}
getSubscriptionMessageTemplates(): SubscriptionMessageTemplateConfig {
@@ -181,6 +197,49 @@ export class UserService {
}))
}
async grantAdminBookingSubscriptionCount(userId: string): Promise<UserProfileResponse> {
const user = await this.prisma.user.findUnique({
where: { id: userId },
include: {
_count: {
select: {
memberships: {
where: { status: MembershipStatus.ACTIVE },
},
},
},
},
})
if (!user) {
throw new NotFoundException('User not found')
}
if (user.role !== UserRole.ADMIN) {
return this.mapProfile(user)
}
const updated = await this.prisma.user.update({
where: { id: userId },
data: {
adminBookingSubscriptionCount: {
increment: ADMIN_BOOKING_SUBSCRIPTION_INCREMENT,
},
},
include: {
_count: {
select: {
memberships: {
where: { status: MembershipStatus.ACTIVE },
},
},
},
},
})
return this.mapProfile(updated)
}
async getStats(userId: string): Promise<UserStatsResponse> {
const completedBookings = await this.prisma.booking.findMany({
where: {