feat(app): 新增个人中心课表视图

This commit is contained in:
richarjiang
2026-04-19 22:23:23 +08:00
parent 9575210b06
commit bd3d519b4f
17 changed files with 998 additions and 29 deletions

View File

@@ -1,14 +1,22 @@
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { User } from '@prisma/client'
import { UserRole } from '@mp-pilates/shared'
import {
MembershipStatus,
SubscriptionMessageScene,
type SubscriptionMessageTemplate,
type SubscriptionMessageTemplateConfig,
type UserProfileResponse,
UserRole,
} from '@mp-pilates/shared'
import { ConfigService } from '@nestjs/config'
import { PrismaService } from '../prisma/prisma.service'
import { WechatService } from './wechat.service'
import { InviteService } from '../invite/invite.service'
export interface LoginResult {
token: string
user: User
user: UserProfileResponse
isNewUser: boolean
}
@@ -57,9 +65,53 @@ export class AuthService {
private readonly jwtService: JwtService,
private readonly wechatService: WechatService,
private readonly inviteService: InviteService,
private readonly configService: ConfigService,
@Inject(RANDOM_FN_TOKEN) private readonly randomFn: () => number = Math.random,
) {}
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: templates.filter((item) => item.templateId),
}
}
private async mapLoginUser(user: User): Promise<UserProfileResponse> {
const activeMembershipCount = await this.prisma.membership.count({
where: {
userId: user.id,
status: MembershipStatus.ACTIVE,
},
})
return {
id: user.id,
phone: user.phone,
nickname: user.nickname,
avatarUrl: user.avatarUrl,
role: user.role as UserRole,
activeMembershipCount,
inviteShareEligible: activeMembershipCount > 0,
adminBookingSubscriptionCount: user.adminBookingSubscriptionCount,
subscriptionMessageTemplates: this.buildSubscriptionTemplateConfig(),
createdAt: user.createdAt.toISOString(),
}
}
async login(
code: string,
nickname?: string,
@@ -96,7 +148,7 @@ export class AuthService {
sessionKeyStore.set(updated.id, sessionKey)
const payload: JwtPayload = { sub: updated.id, role: updated.role as UserRole }
const token = this.jwtService.sign(payload)
return { token, user: updated, isNewUser: false }
return { token, user: await this.mapLoginUser(updated), isNewUser: false }
}
sessionKeyStore.set(user.id, sessionKey)
@@ -108,7 +160,7 @@ export class AuthService {
const payload: JwtPayload = { sub: user.id, role: user.role as UserRole }
const token = this.jwtService.sign(payload)
return { token, user, isNewUser }
return { token, user: await this.mapLoginUser(user), isNewUser }
}
async bindPhone(