feat: 支持管理员消息推送
This commit is contained in:
@@ -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: {
|
||||
|
||||
Reference in New Issue
Block a user