繁体   English   中英

意外的 NestJs 错误:Nest 无法解析 UserService 的依赖项

[英]Unexpected NestJs error: Nest can't resolve dependencies of the UserService

我正在尝试在 NestJs 中构建一个简单的 api,并通过身份验证将食谱保存到 MongoDB。

我试图添加一个 email 服务来为新用户发送一个确认 email 并遇到了一个我自己无法弄清楚的依赖错误。

有问题的错误:

Error: Nest can't resolve dependencies of the UserService (?). Please make sure that the argument UserModel at index [0] is available in the EmailModule context.

Potential solutions:
- Is EmailModule a valid NestJS module?
- If UserModel is a provider, is it part of the current EmailModule?
- If UserModel is exported from a separate @Module, is that module imported within EmailModule?
  @Module({
    imports: [ /* the Module containing UserModel */ ]
  })

    at Injector.lookupComponentInParentModules (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\injector.js:241:19)
    at Injector.resolveComponentInstance (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\injector.js:194:33)
    at resolveParam (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\injector.js:116:38)
    at async Promise.all (index 0)
    at Injector.resolveConstructorParams (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\injector.js:131:27)
    at Injector.loadInstance (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\injector.js:57:13)
    at Injector.loadProvider (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\injector.js:84:9)
    at async Promise.all (index 4)
    at InstanceLoader.createInstancesOfProviders (C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\instance-loader.js:47:9)
    at C:\Users\Jonathan\Documents\Repos\pantry-api\node_modules\@nestjs\core\injector\instance-loader.js:32:13

它指出 EmailModule 中缺少 UserModel,但事实并非如此。

电子邮件模块:

import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { UserModule } from "src/user/user.module";
import { JwtService } from "@nestjs/jwt";
import { UserService } from "src/user/user.service";

@Module({
  imports: [ConfigModule, UserModule],
  controllers: [],
  providers: [JwtService, UserService],
})
export class EmailModule {}

Email 客服:

import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { createTransport } from "nodemailer";
import * as Mail from "nodemailer/lib/mailer";

@Injectable()
export default class EmailService {
  private nodemailerTransport: Mail;

  constructor(private readonly configService: ConfigService) {
    this.nodemailerTransport = createTransport({
      service: configService.get("EMAIL_SERVICE"),
      auth: {
        user: configService.get("EMAIL_USER"),
        pass: configService.get("EMAIL_PASSWORD"),
      },
    });
  }

  sendMail(options: Mail.Options) {
    return this.nodemailerTransport.sendMail(options);
  }
}

Email 确认服务:

import { Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { ConfigService } from "@nestjs/config";
import EmailService from "./email.service";
import { UserService } from "src/user/user.service";
import { AccountStatus } from "src/types";
import { BadRequestException } from "@nestjs/common/exceptions";

interface VerificationTokenPayload {
  email: string;
}

@Injectable()
export class EmailConfirmationService {
  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
    private emailService: EmailService,
    private userService: UserService,
  ) {}

  sendVerificationLink(email: string) {
    const payload: VerificationTokenPayload = { email };
    const token = this.jwtService.sign(payload, {
      secret: this.configService.get("JWT_VERIFICATION_TOKEN_SECRET"),
      expiresIn: `${this.configService.get("JWT_VERIFICATION_TOKEN_EXPIRATION_TIME")}s`,
    });

    const url = `${this.configService.get("EMAIL_CONFIRMATION_URL")}?token=${token}`;

    const text = `Welcome to Pantry! To confirm the email address, click here: ${url}`;

    return this.emailService.sendMail({
      to: email,
      subject: "Pantry Account Confirmation",
      text,
    });
  }

  async confirmEmail(email: string) {
    const user = await this.userService.findOne(email);

    if (user && user.status !== AccountStatus.Created)
      throw new BadRequestException("Email already confirmed");

    await this.userService.markEmailAsConfirmed(email);
  }

  async decodeConfirmationToken(token: string) {
    try {
      const payload = await this.jwtService.verify(token, {
        secret: this.configService.get("JWT_VERIFICATION_TOKEN_SECRET"),
      });

      if (typeof payload === "object" && "email" in payload) {
        return payload.email;
      }
      throw new BadRequestException();
    } catch (error) {
      if (error?.name === "TokenExpiredError") {
        throw new BadRequestException("Email confirmation token expired");
      }
      throw new BadRequestException("Bad confirmation token");
    }
  }

  public async resendConfirmationLink(email: string) {
    const user = await this.userService.findOne(email)
    if (user.status === AccountStatus.Confirmed) {
      throw new BadRequestException('Email already confirmed');
    }
    await this.sendVerificationLink(user.email);
  }
}

如果它们有任何用处,我将在下面添加其他服务和模块。

用户模块:

import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";
import { User, UserSchema } from "./schemas/user.schema";
import { UserController } from "./user.controller";
import { UserService } from "./user.service";

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  controllers: [UserController],
  providers: [UserService]
})
export class UserModule {}

用户服务:

import { BadRequestException, Injectable } from "@nestjs/common";
import { NotFoundException } from "@nestjs/common/exceptions";
import { InjectModel } from "@nestjs/mongoose";
import { Model, isValidObjectId } from "mongoose";
import { AccountStatus } from "src/types";
import { User, UserDocument } from "./schemas/user.schema";
import { MSG_USER_NOT_FOUND } from "./user-messages";

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<UserDocument>) {}

  private readonly defaultProjection = {
    __v: false,
    password: false,
  };

  async findOne(email: string): Promise<User> {
    const user = this.userModel.findOne({ email }, this.defaultProjection);
    if (user === null) throw new NotFoundException(MSG_USER_NOT_FOUND);
    return user;
  }

  async deleteOne(id: string): Promise<any> {
    if (!isValidObjectId(id)) throw new BadRequestException();

    const result = await this.userModel.deleteOne({ _id: id }).exec();

    if (result.deletedCount !== 1) throw new NotFoundException(MSG_USER_NOT_FOUND);
    return result;
  }

  async updateOne(id: string, userData: User) {
    if (!isValidObjectId(id)) throw new BadRequestException();
    let result;

    try {
      result = await this.userModel.findByIdAndUpdate(id, userData).setOptions({ new: true });
    } catch (e) {
      throw new BadRequestException();
    }

    if (result === null) throw new NotFoundException(MSG_USER_NOT_FOUND);
    return result;
  }

  async markEmailAsConfirmed(email: string) {
    const user = await this.findOne(email);
    return this.updateOne(user.email, {...user, status: AccountStatus.Confirmed})
  }
}

授权模块:

import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";
import { MongooseModule } from "@nestjs/mongoose";
import { PassportModule } from "@nestjs/passport";
import { EmailModule } from "src/email/email.module";
import { EmailConfirmationService } from "src/email/emailConfirmation.service";
import { User, UserSchema } from "src/user/schemas/user.schema";
import { UserModule } from "src/user/user.module";
import { UserService } from "src/user/user.service";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./jwt.strategy";
import { LocalStrategy } from "./local.auth";

@Module({
  imports: [
    UserModule,
    EmailModule,
    PassportModule,
    JwtModule.register({ secret: "secretKey", signOptions: { expiresIn: "10m" } }),
    MongooseModule.forFeature([{ name: User.name, schema: UserSchema }]),
  ],
  providers: [
    AuthService,
    JwtStrategy,
    UserService,
    LocalStrategy,
    EmailConfirmationService,
    ConfigService,
  ],
  controllers: [AuthController],
})
export class AuthModule {}

授权服务:

import { Injectable, NotAcceptableException, BadRequestException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { InjectModel } from "@nestjs/mongoose";
import * as bcrypt from "bcrypt";
import {
  MSG_USER_EMAIL_TAKEN,
  MSG_USER_NAME_TAKEN,
  MSG_USER_NOT_FOUND,
  MSG_USER_WRONG_CRED,
} from "src/user/user-messages";
import { UserService } from "../user/user.service";
import { LoginDto } from "./dto/login.dto";
import { RegisterDto } from "./dto/register.dto";
import { Model } from "mongoose";
import { User, UserDocument } from "src/user/schemas/user.schema";
import { AccountStatus } from "src/types";

@Injectable()
export class AuthService {
  constructor(
    @InjectModel(User.name) private userModel: Model<UserDocument>,
    private userService: UserService,
    private jwtService: JwtService,
  ) {}

  async validateUser({ email, password }: LoginDto) {
    const user = await this.userService.findOne(email);
    if (!user) throw new NotAcceptableException(MSG_USER_NOT_FOUND);

    const passwordValid = await bcrypt.compare(password, user.password);

    if (user && passwordValid) return user;
    return null;
  }

  async register({ email, username, password }: RegisterDto) {
    const userWithEmail = await this.userModel.findOne({ email });
    if (userWithEmail) throw new BadRequestException(MSG_USER_EMAIL_TAKEN);

    const userWithName = await this.userModel.findOne({ username });
    if (userWithName) throw new BadRequestException(MSG_USER_NAME_TAKEN);

    const createdUser = new this.userModel({email, username, password});
    createdUser.status = AccountStatus.Created;
    const newUser = await createdUser.save();
    return newUser;
  }

  async login(login: LoginDto) {
    const user = await this.validateUser(login);
    if (!user) throw new NotAcceptableException(MSG_USER_WRONG_CRED);

    const payload = { email: user.email, sub: user._id };

    return {
      access_token: this.jwtService.sign(payload),
    };
  }
}

我希望这足以获得一些帮助,我现在很难共享整个存储库,因为它与工作相关

当您尝试在另一个模块中使用UserService而不是它的注册位置(即UserModule )时,您需要公开它,如下所示:

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  controllers: [UserController],
  providers: [UserService]
  exports: [UserService], // <<<<
})
export class UserModule {}

然后从EmailModule中删除UserService

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM