简体   繁体   English

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

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

I'm trying to build a simple api in NestJs with authentication to save recipes to a MongoDB.我正在尝试在 NestJs 中构建一个简单的 api,并通过身份验证将食谱保存到 MongoDB。

I was trying to add an email service to send a confirmation email for new users and ran into a dependency error I'm not able to figure out myself.我试图添加一个 email 服务来为新用户发送一个确认 email 并遇到了一个我自己无法弄清楚的依赖错误。

The error in question:有问题的错误:

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

It states the UserModel is missing in the EmailModule but that doesn't seem to be the case.它指出 EmailModule 中缺少 UserModel,但事实并非如此。

EmailModule:电子邮件模块:

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 Service: 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 Confirmation Service: 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);
  }
}

I will add the other services & modules below in case they are of any use.如果它们有任何用处,我将在下面添加其他服务和模块。

User Module:用户模块:

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 {}

User Service:用户服务:

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})
  }
}

Auth Module:授权模块:

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 {}

Auth Service:授权服务:

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),
    };
  }
}

I hope this is enough to get some help, it's hard for me to share the entire repository at this point since it's work related我希望这足以获得一些帮助,我现在很难共享整个存储库,因为它与工作相关

as you're trying to use UserService in another module other than where it was registered (which was UserModule ), you need to expose it, like this:当您尝试在另一个模块中使用UserService而不是它的注册位置(即UserModule )时,您需要公开它,如下所示:

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

then remove UserService from EmailModule .然后从EmailModule中删除UserService

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

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