用户静默注册
This commit is contained in:
@@ -12,6 +12,7 @@ export interface AuthUser {
|
||||
actorId: bigint;
|
||||
clientApp: ClientApp;
|
||||
sub: string;
|
||||
phoneVerified: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -39,6 +40,7 @@ export class JwtAuthGuard implements CanActivate {
|
||||
actorId: BigInt(payload.actorId),
|
||||
clientApp,
|
||||
sub: payload.sub,
|
||||
phoneVerified: !!payload.phoneVerified,
|
||||
} satisfies AuthUser;
|
||||
return true;
|
||||
} catch (err) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { CLIENT_APP_ACTOR_MAP, ClientApp } from '@dukang/shared-types';
|
||||
import type { AuthUser } from './jwt-auth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class OptionalJwtAuthGuard implements CanActivate {
|
||||
constructor(private readonly jwtService: JwtService) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const auth = req.headers.authorization as string | undefined;
|
||||
if (!auth?.startsWith('Bearer ')) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
const payload = this.jwtService.verify(auth.slice(7));
|
||||
const clientApp = req.headers['x-client-app'] as ClientApp;
|
||||
if (!clientApp || payload.clientApp !== clientApp) return true;
|
||||
const expectedActor = CLIENT_APP_ACTOR_MAP[clientApp];
|
||||
if (payload.actorType !== expectedActor) return true;
|
||||
req.user = {
|
||||
actorType: payload.actorType,
|
||||
actorId: BigInt(payload.actorId),
|
||||
clientApp,
|
||||
sub: payload.sub,
|
||||
phoneVerified: !!payload.phoneVerified,
|
||||
} satisfies AuthUser;
|
||||
} catch {
|
||||
/* ignore invalid token */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.module';
|
||||
import type { AuthUser } from './jwt-auth.guard';
|
||||
|
||||
@Injectable()
|
||||
export class PhoneVerifiedGuard implements CanActivate {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const req = context.switchToHttp().getRequest();
|
||||
const user = req.user as AuthUser | undefined;
|
||||
if (!user || user.actorType !== 'USER') {
|
||||
throw new ForbiddenException('请先验证手机号');
|
||||
}
|
||||
|
||||
const row = await this.prisma.user.findUnique({
|
||||
where: { id: user.actorId },
|
||||
select: { phoneVerifiedAt: true, mergedIntoUserId: true, status: true },
|
||||
});
|
||||
if (!row || row.status !== 1 || row.mergedIntoUserId) {
|
||||
throw new ForbiddenException('账号状态异常,请重新进入');
|
||||
}
|
||||
if (!row.phoneVerifiedAt) {
|
||||
throw new ForbiddenException('请先验证手机号');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { AuthService } from './auth.service';
|
||||
import { LoginSmsDto, SendSmsDto } from './dto/auth.dto';
|
||||
import {
|
||||
BindPhoneDto,
|
||||
BootstrapSessionDto,
|
||||
LoginSmsDto,
|
||||
RefreshTokenDto,
|
||||
SendSmsDto,
|
||||
} from './dto/auth.dto';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { OptionalJwtAuthGuard } from '../../common/guards/optional-jwt-auth.guard';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
import { AuthUser } from '../../common/guards/jwt-auth.guard';
|
||||
import { ClientApp } from '@dukang/shared-types';
|
||||
@@ -10,14 +18,33 @@ import { ClientApp } from '@dukang/shared-types';
|
||||
export class UserAuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('auth/session/bootstrap')
|
||||
bootstrap(@Body() dto: BootstrapSessionDto) {
|
||||
return this.authService.bootstrapSession(dto.deviceKey, ClientApp.USER_H5);
|
||||
}
|
||||
|
||||
@Post('auth/token/refresh')
|
||||
refresh(@Body() dto: RefreshTokenDto) {
|
||||
return this.authService.refreshAccessToken(dto.refreshToken, ClientApp.USER_H5);
|
||||
}
|
||||
|
||||
@Post('auth/sms/send')
|
||||
sendSms(@Body() dto: SendSmsDto) {
|
||||
return this.authService.sendSms(dto.phone, dto.scene);
|
||||
}
|
||||
|
||||
@Post('auth/login/sms')
|
||||
login(@Body() dto: LoginSmsDto) {
|
||||
return this.authService.loginUser(dto.phone, dto.code, ClientApp.USER_H5);
|
||||
@UseGuards(OptionalJwtAuthGuard)
|
||||
login(@Req() req: Request, @Body() dto: LoginSmsDto) {
|
||||
const guest = (req as Request & { user?: AuthUser }).user;
|
||||
const guestId = guest?.actorType === 'USER' ? guest.actorId : undefined;
|
||||
return this.authService.loginUser(dto.phone, dto.code, ClientApp.USER_H5, guestId);
|
||||
}
|
||||
|
||||
@Post('auth/phone/bind')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
bindPhone(@CurrentUser() user: AuthUser, @Body() dto: BindPhoneDto) {
|
||||
return this.authService.bindPhone(user.actorId, dto.phone, dto.code, ClientApp.USER_H5);
|
||||
}
|
||||
|
||||
@Post('auth/login/wechat')
|
||||
@@ -26,7 +53,7 @@ export class UserAuthController {
|
||||
}
|
||||
|
||||
@Post('auth/wechat/bind-phone')
|
||||
bindPhone() {
|
||||
bindPhoneLegacy() {
|
||||
return this.authService.wechatDisabled();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Inject,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
NotImplementedException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { ClientApp, SmsScene } from '@dukang/shared-types';
|
||||
@@ -11,6 +15,23 @@ import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { SMS_PROVIDER } from '../../integrations/integrations.constants';
|
||||
import { ISmsProvider } from '../../integrations/sms/sms.interface';
|
||||
import { serializeBigInt } from '../../common/decorators/current-user.decorator';
|
||||
import type { Prisma } from '@prisma/client';
|
||||
|
||||
import type { User } from '@prisma/client';
|
||||
|
||||
type UserRow = Pick<
|
||||
User,
|
||||
| 'id'
|
||||
| 'userNo'
|
||||
| 'deviceKey'
|
||||
| 'phone'
|
||||
| 'phoneVerifiedAt'
|
||||
| 'nickname'
|
||||
| 'avatarUrl'
|
||||
| 'wxOpenId'
|
||||
| 'mergedIntoUserId'
|
||||
| 'status'
|
||||
>;
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@@ -25,28 +46,144 @@ export class AuthService {
|
||||
return { sent: true };
|
||||
}
|
||||
|
||||
async loginUser(phone: string, code: string, clientApp: ClientApp) {
|
||||
await this.smsProvider.verify(phone, code, SmsScene.USER_LOGIN);
|
||||
let user = await this.prisma.user.findUnique({ where: { phone } });
|
||||
if (!user) {
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
phone,
|
||||
userNo: generateUserNo(),
|
||||
nickname: `用户${phone.slice(-4)}`,
|
||||
async bootstrapSession(deviceKey: string | undefined, clientApp: ClientApp) {
|
||||
let user: UserRow | null = null;
|
||||
let resolvedDeviceKey = deviceKey?.trim() || null;
|
||||
|
||||
if (resolvedDeviceKey) {
|
||||
user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
deviceKey: resolvedDeviceKey,
|
||||
status: 1,
|
||||
mergedIntoUserId: null,
|
||||
},
|
||||
});
|
||||
await this.prisma.userCityPreference.create({
|
||||
data: { userId: user.id, selectedCityCode: '410100', selectedDistrict: '郑州市' },
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
resolvedDeviceKey = randomUUID();
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
userNo: generateUserNo(),
|
||||
deviceKey: resolvedDeviceKey,
|
||||
nickname: '访客',
|
||||
cityPref: {
|
||||
create: {
|
||||
selectedCityCode: '410100',
|
||||
selectedDistrict: '郑州市',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return this.issueToken('USER', user.id, clientApp, {
|
||||
id: user.id.toString(),
|
||||
userNo: user.userNo,
|
||||
phone: user.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'),
|
||||
nickname: user.nickname,
|
||||
hasWechat: !!user.wxOpenId,
|
||||
});
|
||||
|
||||
return this.buildSessionResponse(user, clientApp, resolvedDeviceKey);
|
||||
}
|
||||
|
||||
async refreshAccessToken(refreshToken: string, clientApp: ClientApp) {
|
||||
try {
|
||||
const payload = this.jwtService.verify(refreshToken);
|
||||
if (payload.clientApp !== clientApp || payload.actorType !== 'USER') {
|
||||
throw new UnauthorizedException('Invalid refresh token');
|
||||
}
|
||||
const user = await this.assertActiveUser(BigInt(payload.actorId));
|
||||
return this.buildSessionResponse(user, clientApp, user.deviceKey);
|
||||
} catch (err) {
|
||||
if (err instanceof UnauthorizedException) throw err;
|
||||
throw new UnauthorizedException('Invalid refresh token');
|
||||
}
|
||||
}
|
||||
|
||||
async loginUser(phone: string, code: string, clientApp: ClientApp, guestId?: bigint) {
|
||||
await this.smsProvider.verify(phone, code, SmsScene.USER_LOGIN);
|
||||
let user: UserRow | null = await this.prisma.user.findUnique({ where: { phone } });
|
||||
|
||||
if (!user) {
|
||||
if (guestId) {
|
||||
try {
|
||||
const guest = await this.assertActiveUser(guestId);
|
||||
if (!guest.phone) {
|
||||
user = await this.prisma.user.update({
|
||||
where: { id: guestId },
|
||||
data: {
|
||||
phone,
|
||||
phoneVerifiedAt: new Date(),
|
||||
nickname: guest.nickname === '访客' ? `用户${phone.slice(-4)}` : guest.nickname,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
/* guest invalid, fall through to create */
|
||||
}
|
||||
}
|
||||
if (!user) {
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
phone,
|
||||
phoneVerifiedAt: new Date(),
|
||||
userNo: generateUserNo(),
|
||||
nickname: `用户${phone.slice(-4)}`,
|
||||
cityPref: {
|
||||
create: {
|
||||
selectedCityCode: '410100',
|
||||
selectedDistrict: '郑州市',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!user.phoneVerifiedAt) {
|
||||
user = await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { phoneVerifiedAt: new Date() },
|
||||
});
|
||||
}
|
||||
if (guestId && guestId !== user.id) {
|
||||
user = await this.mergeUsers(guestId, user.id);
|
||||
} else {
|
||||
await this.assertActiveUser(user.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!user) throw new BadRequestException('登录失败');
|
||||
|
||||
return this.buildSessionResponse(user, clientApp, user.deviceKey);
|
||||
}
|
||||
|
||||
async bindPhone(actorId: bigint, phone: string, code: string, clientApp: ClientApp) {
|
||||
await this.smsProvider.verify(phone, code, SmsScene.BIND_PHONE);
|
||||
const guest = await this.assertActiveUser(actorId);
|
||||
|
||||
if (guest.phone && guest.phoneVerifiedAt) {
|
||||
if (guest.phone === phone) {
|
||||
return this.buildSessionResponse(guest, clientApp, guest.deviceKey);
|
||||
}
|
||||
throw new BadRequestException('当前账号已绑定其他手机号');
|
||||
}
|
||||
|
||||
const existing = await this.prisma.user.findUnique({ where: { phone } });
|
||||
let targetUser: UserRow;
|
||||
|
||||
if (!existing) {
|
||||
targetUser = await this.prisma.user.update({
|
||||
where: { id: guest.id },
|
||||
data: {
|
||||
phone,
|
||||
phoneVerifiedAt: new Date(),
|
||||
nickname: guest.nickname === '访客' ? `用户${phone.slice(-4)}` : guest.nickname,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await this.assertActiveUser(existing.id);
|
||||
if (existing.id === guest.id) {
|
||||
targetUser = existing;
|
||||
} else {
|
||||
targetUser = await this.mergeUsers(guest.id, existing.id);
|
||||
}
|
||||
}
|
||||
|
||||
return this.buildSessionResponse(targetUser, clientApp, targetUser.deviceKey);
|
||||
}
|
||||
|
||||
async loginStore(phone: string, code: string, clientApp: ClientApp) {
|
||||
@@ -60,7 +197,7 @@ export class AuthService {
|
||||
where: { id: account.id },
|
||||
data: { lastLoginAt: new Date() },
|
||||
});
|
||||
return this.issueToken('STORE', account.id, clientApp, undefined, {
|
||||
return this.issueToken('STORE', account.id, clientApp, false, undefined, {
|
||||
id: account.id.toString(),
|
||||
storeId: account.storeId.toString(),
|
||||
name: account.name,
|
||||
@@ -80,7 +217,7 @@ export class AuthService {
|
||||
where: { id: account.id },
|
||||
data: { lastLoginAt: new Date() },
|
||||
});
|
||||
return this.issueToken('PARTNER', account.id, clientApp, undefined, undefined, {
|
||||
return this.issueToken('PARTNER', account.id, clientApp, false, undefined, undefined, {
|
||||
id: account.id.toString(),
|
||||
partnerId: account.partnerId.toString(),
|
||||
name: account.name,
|
||||
@@ -92,8 +229,8 @@ export class AuthService {
|
||||
|
||||
async getMe(actorType: string, actorId: bigint) {
|
||||
if (actorType === 'USER') {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: actorId } });
|
||||
return serializeBigInt(user);
|
||||
const user = await this.assertActiveUser(actorId);
|
||||
return this.formatUserProfile(user);
|
||||
}
|
||||
if (actorType === 'STORE') {
|
||||
const account = await this.prisma.storeAccount.findUnique({
|
||||
@@ -116,27 +253,128 @@ export class AuthService {
|
||||
throw new NotImplementedException('FEATURE_DISABLED');
|
||||
}
|
||||
|
||||
private async mergeUsers(guestId: bigint, primaryId: bigint): Promise<UserRow> {
|
||||
if (guestId === primaryId) {
|
||||
return this.assertActiveUser(primaryId);
|
||||
}
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
const guest = await tx.user.findUnique({ where: { id: guestId } });
|
||||
const primary = await tx.user.findUnique({ where: { id: primaryId } });
|
||||
if (!guest || guest.mergedIntoUserId || guest.status !== 1) {
|
||||
throw new BadRequestException('访客账号无效');
|
||||
}
|
||||
if (!primary || primary.mergedIntoUserId || primary.status !== 1) {
|
||||
throw new BadRequestException('目标账号无效');
|
||||
}
|
||||
|
||||
await tx.order.updateMany({ where: { userId: guestId }, data: { userId: primaryId } });
|
||||
await tx.userAddress.updateMany({ where: { userId: guestId }, data: { userId: primaryId } });
|
||||
await tx.benefitCoupon.updateMany({ where: { userId: guestId }, data: { userId: primaryId } });
|
||||
await tx.benefitLedger.updateMany({ where: { userId: guestId }, data: { userId: primaryId } });
|
||||
await tx.redeemRecord.updateMany({ where: { userId: guestId }, data: { userId: primaryId } });
|
||||
await tx.eventLog.updateMany({ where: { userId: guestId }, data: { userId: primaryId } });
|
||||
|
||||
const primaryPref = await tx.userCityPreference.findUnique({ where: { userId: primaryId } });
|
||||
const guestPref = await tx.userCityPreference.findUnique({ where: { userId: guestId } });
|
||||
if (!primaryPref && guestPref) {
|
||||
await tx.userCityPreference.update({
|
||||
where: { userId: guestId },
|
||||
data: { userId: primaryId },
|
||||
});
|
||||
} else if (guestPref) {
|
||||
await tx.userCityPreference.delete({ where: { userId: guestId } });
|
||||
}
|
||||
|
||||
const guestPromo = await tx.userPromoAttribution.findUnique({ where: { userId: guestId } });
|
||||
if (guestPromo) {
|
||||
const primaryPromo = await tx.userPromoAttribution.findUnique({ where: { userId: primaryId } });
|
||||
if (primaryPromo) {
|
||||
await tx.userPromoAttribution.delete({ where: { userId: guestId } });
|
||||
} else {
|
||||
await tx.userPromoAttribution.update({
|
||||
where: { userId: guestId },
|
||||
data: { userId: primaryId },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const primaryUpdate: Prisma.UserUpdateInput = {};
|
||||
if (guest.deviceKey && !primary.deviceKey) {
|
||||
primaryUpdate.deviceKey = guest.deviceKey;
|
||||
}
|
||||
if (Object.keys(primaryUpdate).length > 0) {
|
||||
await tx.user.update({ where: { id: primaryId }, data: primaryUpdate });
|
||||
}
|
||||
|
||||
await tx.user.update({
|
||||
where: { id: guestId },
|
||||
data: {
|
||||
mergedIntoUserId: primaryId,
|
||||
status: 0,
|
||||
deviceKey: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return this.assertActiveUser(primaryId);
|
||||
}
|
||||
|
||||
private async assertActiveUser(userId: bigint): Promise<UserRow> {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) throw new NotFoundException('用户不存在');
|
||||
if (user.mergedIntoUserId) {
|
||||
throw new UnauthorizedException('账号已合并,请重新进入');
|
||||
}
|
||||
if (user.status !== 1) {
|
||||
throw new ForbiddenException('账号已停用');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
private buildSessionResponse(user: UserRow, clientApp: ClientApp, deviceKey: string | null) {
|
||||
const phoneVerified = !!user.phoneVerifiedAt;
|
||||
return this.issueToken('USER', user.id, clientApp, phoneVerified, this.formatUserProfile(user), undefined, undefined, deviceKey);
|
||||
}
|
||||
|
||||
private formatUserProfile(user: UserRow) {
|
||||
return {
|
||||
id: user.id.toString(),
|
||||
userNo: user.userNo,
|
||||
phone: user.phone ? user.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : null,
|
||||
phoneVerified: !!user.phoneVerifiedAt,
|
||||
nickname: user.nickname,
|
||||
avatarUrl: user.avatarUrl,
|
||||
hasWechat: !!user.wxOpenId,
|
||||
};
|
||||
}
|
||||
|
||||
private issueToken(
|
||||
actorType: string,
|
||||
actorId: bigint,
|
||||
clientApp: ClientApp,
|
||||
phoneVerified: boolean,
|
||||
user?: Record<string, unknown>,
|
||||
store?: Record<string, unknown>,
|
||||
partner?: Record<string, unknown>,
|
||||
deviceKey?: string | null,
|
||||
) {
|
||||
const payload = {
|
||||
sub: actorId.toString(),
|
||||
actorType,
|
||||
actorId: actorId.toString(),
|
||||
clientApp,
|
||||
phoneVerified,
|
||||
};
|
||||
const accessToken = this.jwtService.sign(payload);
|
||||
const refreshToken = this.jwtService.sign(payload, { expiresIn: '30d' });
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken,
|
||||
deviceKey: deviceKey ?? undefined,
|
||||
actorType,
|
||||
actorId: actorId.toString(),
|
||||
phoneVerified,
|
||||
user,
|
||||
store,
|
||||
partner,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class SendSmsDto {
|
||||
@IsString()
|
||||
@@ -19,3 +19,25 @@ export class LoginSmsDto {
|
||||
@IsNotEmpty()
|
||||
code: string;
|
||||
}
|
||||
|
||||
export class BootstrapSessionDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
deviceKey?: string;
|
||||
}
|
||||
|
||||
export class RefreshTokenDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
refreshToken: string;
|
||||
}
|
||||
|
||||
export class BindPhoneDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
phone: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
code: string;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
import { UserAddressController } from './user-address.controller';
|
||||
import { UserAddressService } from './user-address.service';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
import { PhoneVerifiedGuard } from '../../common/guards/phone-verified.guard';
|
||||
import { OptionalJwtAuthGuard } from '../../common/guards/optional-jwt-auth.guard';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -27,7 +29,7 @@ import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
UserProfileController,
|
||||
UserAddressController,
|
||||
],
|
||||
providers: [AuthService, UserAddressService, JwtAuthGuard],
|
||||
exports: [AuthService, JwtModule, JwtAuthGuard],
|
||||
providers: [AuthService, UserAddressService, JwtAuthGuard, PhoneVerifiedGuard, OptionalJwtAuthGuard],
|
||||
exports: [AuthService, JwtModule, JwtAuthGuard, PhoneVerifiedGuard, OptionalJwtAuthGuard],
|
||||
})
|
||||
export class IamModule {}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Body, Controller, Get, Param, Post, Put, Query, Req, UseGuards } from '
|
||||
import type { Request } from 'express';
|
||||
import { TradeService } from './trade.service';
|
||||
import { JwtAuthGuard, AuthUser } from '../../common/guards/jwt-auth.guard';
|
||||
import { PhoneVerifiedGuard } from '../../common/guards/phone-verified.guard';
|
||||
import { CurrentUser } from '../../common/decorators/current-user.decorator';
|
||||
|
||||
@Controller('trade/orders')
|
||||
@@ -15,6 +16,7 @@ export class TradeController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(PhoneVerifiedGuard)
|
||||
create(@CurrentUser() user: AuthUser, @Body() body: Record<string, unknown>, @Req() req: Request) {
|
||||
return this.tradeService.createOrder(user.actorId, body as never, req);
|
||||
}
|
||||
@@ -35,6 +37,7 @@ export class TradeController {
|
||||
}
|
||||
|
||||
@Post(':id/pay')
|
||||
@UseGuards(PhoneVerifiedGuard)
|
||||
pay(@CurrentUser() user: AuthUser, @Param('id') id: string) {
|
||||
return this.tradeService.payOrder(user.actorId, BigInt(id));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user