@nestjs/core#REQUEST TypeScript Examples

The following examples show how to use @nestjs/core#REQUEST. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: loaders-explorer.service.ts    From nestjs-mercurius with MIT License 6 votes vote down vote up
private registerContextProvider<T = any>(request: T, contextId: ContextId) {
    const coreModuleArray = [...this.modulesContainer.entries()]
      .filter(
        ([key, { metatype }]) =>
          metatype && metatype.name === InternalCoreModule.name,
      )
      .map(([key, value]) => value);

    const coreModuleRef = head(coreModuleArray);
    if (!coreModuleRef) {
      return;
    }
    const wrapper = coreModuleRef.getProviderByKey(REQUEST);
    wrapper.setInstanceByContextId(contextId, {
      instance: request,
      isResolved: true,
    });
  }
Example #2
Source File: SessionManager.ts    From remix-hexagonal-architecture with MIT License 6 votes vote down vote up
@Injectable({ scope: Scope.REQUEST })
export class SessionManager {
  private storage;

  constructor(
    @Inject(SESSION_CONFIG)
    private readonly options: SessionIdStorageStrategy["cookie"],
    @Inject(REQUEST)
    private readonly request: Request
  ) {
    this.storage = createCookieSessionStorage({ cookie: options });
  }

  get() {
    return this.storage.getSession(this.request.headers.cookie);
  }

  commit(session: Session) {
    return this.storage.commitSession(session);
  }

  async destroy() {
    const session = await this.storage.getSession(this.request.headers.cookie);
    return this.storage.destroySession(session);
  }
}
Example #3
Source File: tenancy-core.module.ts    From nestjs-tenancy with MIT License 6 votes vote down vote up
/**
     * Create tenant context provider
     *
     * @private
     * @static
     * @returns {Provider}
     * @memberof TenancyCoreModule
     */
    private static createTenantContextProvider(): Provider {
        return {
            provide: TENANT_CONTEXT,
            scope: Scope.REQUEST,
            useFactory: (
                req: Request,
                moduleOptions: TenancyModuleOptions,
                adapterHost: HttpAdapterHost,
            ) => this.getTenant(req, moduleOptions, adapterHost),
            inject: [
                REQUEST,
                TENANT_MODULE_OPTIONS,
                DEFAULT_HTTP_ADAPTER_HOST,
            ]
        }
    }
Example #4
Source File: joi.pipe.ts    From nestjs-joi with MIT License 6 votes vote down vote up
constructor(
    @Inject(REQUEST) private readonly arg?: unknown,
    @Optional() @Inject(JOIPIPE_OPTIONS) pipeOpts?: JoiPipeOptions,
  ) {
    if (arg) {
      // Test for an actual request object, which indicates we're in "injected" mode.
      // This is the case that requires the most performant handling, which is why it
      // should be the first branch.
      if (isHttpRequest(arg)) {
        this.method = arg.method.toUpperCase();
      } else if (isGraphQlRequest(arg)) {
        // @nestjs/graphql, or rather apollo, only supports GET and POST.
        // To provide a consistent experience without hard to understand behavior
        // such as UPDATE group schemas being ignored, we will NOT set the method.
        // JoiPipe will work for this case, but ignore the method.
        // this.method = arg.req.method.toUpperCase();
      } else {
        // This is the "manually called constructor" case, where performance is
        // (ostensibly) not as big of a concern since manual JoiPipes will be
        // constructed only once at app initialization
        if (Joi.isSchema(arg)) {
          this.schema = arg as Joi.Schema;
        } else if (typeof arg === 'function') {
          this.type = arg as Constructor;
        } else {
          // Options passed as first parameter
          pipeOpts = arg as JoiPipeOptions;
        }
      }
    } else {
      // Called without arguments, do nothing
    }

    this.pipeOpts = this.parseOptions(pipeOpts);
  }
Example #5
Source File: SessionManager.ts    From remix-hexagonal-architecture with MIT License 5 votes vote down vote up
constructor(
    @Inject(SESSION_CONFIG)
    private readonly options: SessionIdStorageStrategy["cookie"],
    @Inject(REQUEST)
    private readonly request: Request
  ) {
    this.storage = createCookieSessionStorage({ cookie: options });
  }
Example #6
Source File: base.service.ts    From codeclannigeria-backend with MIT License 5 votes vote down vote up
@Optional()
  @Inject(REQUEST)
  protected readonly req: Request;
Example #7
Source File: doubt.service.ts    From edu-server with MIT License 5 votes vote down vote up
constructor(
    @InjectModel('Doubt') private readonly DoubtModel: Model<Doubt>,
    @InjectModel('Course') private readonly CourseModel: Model<Course>,
    @InjectModel('User') private readonly UserModel: Model<User>,
    @InjectModel('DoubtAnswer')
    private readonly DoubtAnswerModel: Model<DoubtAnswer>,
    @Inject(REQUEST) private readonly request: Request,
  ) {}
Example #8
Source File: user.service.ts    From edu-server with MIT License 5 votes vote down vote up
constructor(
    @InjectModel('User') private readonly userModel: Model<User>,
    @InjectModel('Course') private readonly courseModel: Model<Course>,
    @InjectModel('Enrolled') private readonly enrolledModel: Model<Enrolled>,
    @Inject(REQUEST) private readonly request: Request,
  ) {}
Example #9
Source File: bull.tokens.ts    From nestjs-bullmq with MIT License 5 votes vote down vote up
JOB_REF = REQUEST
Example #10
Source File: post.service.ts    From nestjs-rest-sample with GNU General Public License v3.0 5 votes vote down vote up
constructor(
    @Inject(POST_MODEL) private postModel: Model<Post>,
    @Inject(COMMENT_MODEL) private commentModel: Model<Comment>,
    @Inject(REQUEST) private req: AuthenticatedRequest,
  ) { }
Example #11
Source File: doubt.service.ts    From edu-server with MIT License 4 votes vote down vote up
@Injectable({ scope: Scope.REQUEST })
export class DoubtService {
  constructor(
    @InjectModel('Doubt') private readonly DoubtModel: Model<Doubt>,
    @InjectModel('Course') private readonly CourseModel: Model<Course>,
    @InjectModel('User') private readonly UserModel: Model<User>,
    @InjectModel('DoubtAnswer')
    private readonly DoubtAnswerModel: Model<DoubtAnswer>,
    @Inject(REQUEST) private readonly request: Request,
  ) {}

  // fetch all Doubts
  async getAllDoubts(skip: string) {
    try {
      const skipNum = parseInt(skip, 10);
      const doubts = await this.DoubtModel.find(
        {},
        {},
        { skip: skipNum, limit: 10 },
      )
        .populate('answers')
        .lean();
      return doubts;
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Get a single Doubt
  async getDoubtById(doubtId: Schema.Types.ObjectId) {
    try {
      const doubt = await this.DoubtModel.findById(doubtId)
        .populate('answers')
        .lean();

      if (doubt) {
        return doubt;
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
    throw new NotFoundException('Error, doubt not found');
  }

  // add a new doubt
  async addNewDoubt(
    courseId: Schema.Types.ObjectId,
    createDoubtDto: CreateDoubtDto,
  ) {
    try {
      const user = await this.UserModel.findOne({
        email: this.request['user']['email'],
      });
      const course = await this.CourseModel.findById(courseId);
      if (course) {
        const doubtToBeCreated = {
          ...createDoubtDto,
          photoUrl: user.photoUrl,
        };
        const newDoubt = await new this.DoubtModel(doubtToBeCreated).save();
        course.doubts.push(newDoubt);
        await course.save();
        return newDoubt.populate('answers');
      } else {
        throw new NotFoundException(
          'The course id is invalid or the course no longer exists',
        );
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // edit the doubt by Id
  async editDoubt(
    courseId: Schema.Types.ObjectId,
    doubtId: Schema.Types.ObjectId,
    updateDoubtDto: UpdateDoubtDto,
  ) {
    try {
      const course = await this.CourseModel.findById(courseId);
      if (course) {
        let updatedDoubt = null;
        updatedDoubt = await this.DoubtModel.findByIdAndUpdate(
          doubtId,
          updateDoubtDto,
          { new: true },
        ).lean();
        if (updatedDoubt) {
          return updatedDoubt;
        } else {
          throw new NotFoundException(
            'The doubt id is invalid or the doubt no longer exists',
          );
        }
      } else {
        throw new NotFoundException(
          'The course id is invalid or the course no longer exists',
        );
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Delete a doubt by Id
  async deleteDoubt(
    courseId: Schema.Types.ObjectId,
    doubtId: Schema.Types.ObjectId,
  ): Promise<any> {
    try {
      const course = await this.CourseModel.findById(courseId);
      if (course) {
        let deletedDoubt = null;
        deletedDoubt = await this.DoubtModel.findByIdAndRemove(doubtId)
          .populate('answers')
          .lean();
        if (deletedDoubt) {
          return deletedDoubt;
        } else {
          throw new NotFoundException(
            'The doubt id is invalid or the doubt no longer exists',
          );
        }
      } else {
        throw new NotFoundException(
          'The course id is invalid or the course no longer exists',
        );
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // add a new doubtAnswer
  async addNewDoubtAnswer(
    doubtId: Schema.Types.ObjectId,
    createDoubtAnswerDto: CreateDoubtAnswerDto,
  ) {
    try {
      const doubt = await this.DoubtModel.findById(doubtId);
      const user = await this.UserModel.findOne({
        email: this.request['user']['email'],
      });
      if (doubt) {
        const doubtAnswerToBeCreated = {
          ...createDoubtAnswerDto,
          photoUrl: user.photoUrl,
        };
        const newDoubtAnswer = await new this.DoubtAnswerModel(
          doubtAnswerToBeCreated,
        ).save();
        doubt.answers.push(newDoubtAnswer);
        await doubt.save();
        return newDoubtAnswer;
      } else {
        throw new NotFoundException(
          'The doubt id is invalid or the doubt no longer exists',
        );
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // edit the doubtAnswer by Id
  async editDoubtAnswer(
    doubtId: Schema.Types.ObjectId,
    doubtAnswerId: Schema.Types.ObjectId,
    updateDoubtAnswerDto: UpdateDoubtAnswerDto,
  ) {
    try {
      const doubt = await this.DoubtModel.findById(doubtId);
      if (doubt) {
        let updatedDoubt = null;
        updatedDoubt = await this.DoubtAnswerModel.findByIdAndUpdate(
          doubtAnswerId,
          updateDoubtAnswerDto,
          { new: true },
        );
        if (updatedDoubt) {
          return updatedDoubt;
        } else {
          throw new NotFoundException(
            'The doubtAnswerId id is invalid or the doubtAnswerId no longer exists',
          );
        }
      } else {
        throw new NotFoundException(
          'The doubt id is invalid or the doubt no longer exists',
        );
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Delete a doubtAnswer by Id
  async deleteDoubtAnswer(
    doubtId: Schema.Types.ObjectId,
    doubtAnswerId: Schema.Types.ObjectId,
  ): Promise<any> {
    try {
      const doubt = await this.DoubtModel.findById(doubtId);
      if (doubt) {
        let deletedDoubt = null;
        deletedDoubt = await this.DoubtAnswerModel.findByIdAndRemove(
          doubtAnswerId,
        );
        if (deletedDoubt) {
          return deletedDoubt;
        } else {
          throw new NotFoundException(
            'The doubtAnswerId id is invalid or the doubtAnswerId no longer exists',
          );
        }
      } else {
        throw new NotFoundException(
          'The doubt id is invalid or the doubt no longer exists',
        );
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // get doubts for selected courses
  async findDoubtsForSelectedCourse(
    courseId: Schema.Types.ObjectId,
  ): Promise<any> {
    try {
      const Course = await this.CourseModel.findById(courseId)
        .populate({
          path: 'doubts',
          model: 'Doubt',
          populate: {
            path: 'answers',
            model: 'DoubtAnswer',
          },
        })
        .exec();
      if (Course) {
        const { doubts } = Course;
        return doubts;
      } else {
        throw new NotFoundException('course not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }
}
Example #12
Source File: user.service.ts    From edu-server with MIT License 4 votes vote down vote up
@Injectable({ scope: Scope.REQUEST })
export class UserService {
  constructor(
    @InjectModel('User') private readonly userModel: Model<User>,
    @InjectModel('Course') private readonly courseModel: Model<Course>,
    @InjectModel('Enrolled') private readonly enrolledModel: Model<Enrolled>,
    @Inject(REQUEST) private readonly request: Request,
  ) {}

  // fetch all Users
  async getAllUser(): Promise<User[]> {
    try {
      const users = await this.userModel.find().exec();
      return users;
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // fetch all gamification data
  async getAllGamified(skip: string): Promise<User[]> {
    try {
      const skipNum = parseInt(skip, 10);
      const users = await this.userModel
        .find({}, { _id: false }, { skip: skipNum, limit: 10 })
        .select('first_name last_name score photoUrl')
        .sort({ score: -1 })
        .exec();
      return users;
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Get a single User
  async findUserByEmail(query): Promise<User> {
    try {
      const { email } = query;
      const user = await this.userModel.findOne({ email });

      if (user) {
        return user;
      } else {
        throw new NotFoundException(`user with email ${email} not Found`);
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Get a single User
  async getMe(): Promise<User> {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });

      if (user) {
        return user;
      } else {
        throw new NotFoundException("Your email doesn't Exist in database");
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // post a single User
  async addUser(request, CreateUserDTO: CreateUserDTO): Promise<User> {
    try {
      const { email, fId, role } = request['user'];
      const userExists = await this.userModel.findOne({ email: email }).lean();
      if (userExists) {
        throw new ConflictException(`User with email ${email} already exists`);
      }
      const userToBeCreated = { ...CreateUserDTO, email, fId, role };
      const newUser = await new this.userModel(userToBeCreated);
      return newUser.save();
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Edit User details
  async updateUser(UpdateUserDTO: UpdateUserDTO): Promise<User> {
    let updatedUser;
    const filter = { email: this.request['user']['email'] };
    try {
      updatedUser = await this.userModel.findOneAndUpdate(
        filter,
        UpdateUserDTO,
        { new: true, useFindAndModify: false },
      );
    } catch (e) {
      throw new InternalServerErrorException(e);
    } finally {
      return updatedUser;
    }
  }

  // Delete a User
  async deleteUser(query: any): Promise<any> {
    try {
      const deletedUser = await this.userModel.findOneAndDelete(query);
      if (deletedUser) {
        return deletedUser;
      } else {
        throw new NotFoundException('User not Found or query not correct!');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // gets all Enrolled courses
  async getEnrolledCoursesById(courseId: Schema.Types.ObjectId) {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        const userId = user.id;
        const enrolledCourses = await this.enrolledModel.findOne({
          studentId: userId,
          courseId: courseId,
        });
        return enrolledCourses;
      } else {
        throw new NotFoundException('User not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // gets all Enrolled courses
  async getEnrolledCourses() {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        const userId = user.id;
        const enrolledCourses = await this.enrolledModel.find({
          studentId: userId,
        });
        return enrolledCourses;
      } else {
        throw new NotFoundException('User not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // adds Enrolled Course
  async addCourse(createEnrolledDTO: CreateEnrolledDTO) {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        const courseIdSearch = await this.enrolledModel
          .find({ studentId: user.id })
          .lean();
        courseIdSearch.forEach((singleEnrolled) => {
          if (singleEnrolled.courseId == createEnrolledDTO['courseId']) {
            throw new ConflictException('Course Already Enrolled by user');
          }
        });
        const newEnrolled = await new this.enrolledModel(createEnrolledDTO);

        const course = await this.courseModel.findById(
          createEnrolledDTO.courseId,
        );

        if (course) {
          newEnrolled['videosWatched'] = new Array(course.video_num).fill(
            false,
          );
          await newEnrolled.save();
          return newEnrolled;
        } else {
          throw new NotFoundException('course not found!');
        }

        // a test line to see the populated sets of data
        /*const newF = await this.enrolledModel.find({}).populate('students');
      return newF;*/
      } else {
        throw new NotFoundException('user not found!');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // gets all wishlisted courses
  async getWishList(): Promise<any> {
    try {
      const userWishList = await this.userModel
        .findOne({
          email: this.request['user']['email'],
        })
        .lean();
      return userWishList.wishlist;
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // adds wishlisted course
  async addWishlist(cId: Schema.Types.ObjectId) {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });

      if (user) {
        const doesWishlistExists = await this.courseModel.exists({
          _id: cId['cId'],
        });
        if (doesWishlistExists) {
          const doesUserExistInWishList = user.wishlist.includes(cId['cId']);
          if (!doesUserExistInWishList) {
            user.wishlist.push(cId['cId']);
            await user.save();
            return user;
          } else {
            throw new ConflictException('Course Already Exists In WishList');
          }
        } else {
          throw new NotFoundException("Wishlisted Course doesn't exist");
        }
      } else {
        throw new NotFoundException('User Not Found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Delete a wishList of User
  async deleteWishList(wishId: Schema.Types.ObjectId): Promise<any> {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        user.wishlist = user.wishlist.filter((wishlist) => wishlist != wishId);
        await user.save();
        return user;
      } else {
        throw new NotFoundException('not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // gets all courses on cartList
  async getCartList(): Promise<any> {
    try {
      const userCartList = await this.userModel
        .findOne({
          email: this.request['user']['email'],
        })
        .lean();
      return userCartList.cartList;
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // adds a course to Cart
  async addCartList(cId: Schema.Types.ObjectId) {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });

      if (user) {
        const doesCartListExists = await this.courseModel.exists({
          _id: cId['cId'],
        });
        if (doesCartListExists) {
          const doesUserExistInCartList = user.cartList.includes(cId['cId']);
          if (!doesUserExistInCartList) {
            user.cartList.push(cId['cId']);
            await user.save();
            return user;
          } else {
            throw new ConflictException('Course Already Exists In Cart');
          }
        } else {
          throw new NotFoundException("Course doesn't exist");
        }
      } else {
        throw new NotFoundException('User Not Found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Delete a course from cart of user
  async deleteCardList(cartId: Schema.Types.ObjectId): Promise<any> {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        user.cartList = user.cartList.filter((cartList) => cartList != cartId);
        await user.save();
        return user;
      } else {
        throw new NotFoundException('User not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // update Enrolled Course
  async updateCourse(
    updateEnrolledDto: UpdateEnrolledDTO,
    courseId: Schema.Types.ObjectId,
  ): Promise<any> {
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        const userId = user.id;
        const updatedCourse = await this.enrolledModel.findOneAndUpdate(
          {
            studentId: userId,
            courseId: courseId,
          },
          updateEnrolledDto,
          { new: true, useFindAndModify: false },
        );
        return updatedCourse;
      } else {
        throw new NotFoundException('User not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }

  // Delete Enrolled Course of User
  async deleteEnrolledCourse(courseId: Schema.Types.ObjectId): Promise<any> {
    let deletedFrom;
    try {
      const user = await this.userModel.findOne({
        email: this.request['user']['email'],
      });
      if (user) {
        const userId = user.id;
        deletedFrom = await this.enrolledModel.findOneAndRemove({
          studentId: userId,
          courseId: courseId,
        });
        if (deletedFrom) {
          return deletedFrom;
        } else {
          throw new NotFoundException('not found');
        }
      } else {
        throw new NotFoundException('User not found');
      }
    } catch (e) {
      throw new InternalServerErrorException(e);
    }
  }
}
Example #13
Source File: post.service.spec.ts    From nestjs-rest-sample with GNU General Public License v3.0 4 votes vote down vote up
describe('PostService', () => {
  let service: PostService;
  let model: Model<Post>;
  let commentModel: Model<Comment>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PostService,
        {
          provide: POST_MODEL,
          useValue: {
            new: jest.fn(),
            constructor: jest.fn(),
            find: jest.fn(),
            findOne: jest.fn(),
            update: jest.fn(),
            create: jest.fn(),
            remove: jest.fn(),
            exec: jest.fn(),
            deleteMany: jest.fn(),
            deleteOne: jest.fn(),
            updateOne: jest.fn(),
            findOneAndUpdate: jest.fn(),
            findOneAndDelete: jest.fn(),
          },
        },
        {
          provide: COMMENT_MODEL,
          useValue: {
            new: jest.fn(),
            constructor: jest.fn(),
            find: jest.fn(),
            findOne: jest.fn(),
            updateOne: jest.fn(),
            deleteOne: jest.fn(),
            update: jest.fn(),
            create: jest.fn(),
            remove: jest.fn(),
            exec: jest.fn(),
          },
        },
        {
          provide: REQUEST,
          useValue: {
            user: {
              id: 'dummyId',
            },
          },
        },
      ],
    }).compile();

    service = await module.resolve<PostService>(PostService);
    model = module.get<Model<Post>>(POST_MODEL);
    commentModel = module.get<Model<Comment>>(COMMENT_MODEL);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('findAll should return all posts', async () => {
    const posts = [
      {
        _id: '5ee49c3115a4e75254bb732e',
        title: 'Generate a NestJS project',
        content: 'content',
      },
      {
        _id: '5ee49c3115a4e75254bb732f',
        title: 'Create CRUD RESTful APIs',
        content: 'content',
      },
      {
        _id: '5ee49c3115a4e75254bb7330',
        title: 'Connect to MongoDB',
        content: 'content',
      },
    ];
    jest.spyOn(model, 'find').mockReturnValue({
      skip: jest.fn().mockReturnValue({
        limit: jest.fn().mockReturnValue({
          exec: jest.fn().mockResolvedValueOnce(posts) as any,
        }),
      }),
    } as any);

    const data = await lastValueFrom(service.findAll());
    expect(data.length).toBe(3);
    expect(model.find).toHaveBeenCalled();

    jest
      .spyOn(model, 'find')
      .mockImplementation(
        (
          conditions: FilterQuery<Post>,
          callback?: (err: any, res: Post[]) => void,
        ) => {
          return {
            skip: jest.fn().mockReturnValue({
              limit: jest.fn().mockReturnValue({
                exec: jest.fn().mockResolvedValueOnce([posts[0]]),
              }),
            }),
          } as any;
        },
      );

    const result = await lastValueFrom(service.findAll('Generate', 0, 10));
    expect(result.length).toBe(1);
    expect(model.find).lastCalledWith({
      title: { $regex: '.*' + 'Generate' + '.*' },
    });
  });

  describe('findByid', () => {
    it('if exists return one post', (done) => {
      const found = {
        _id: '5ee49c3115a4e75254bb732e',
        title: 'Generate a NestJS project',
        content: 'content',
      };

      jest.spyOn(model, 'findOne').mockReturnValue({
        exec: jest.fn().mockResolvedValueOnce(found) as any,
      } as any);

      service.findById('1').subscribe({
        next: (data) => {
          expect(data._id).toBe('5ee49c3115a4e75254bb732e');
          expect(data.title).toEqual('Generate a NestJS project');
        },
        error: (error) => console.log(error),
        complete: done(),
      });
    });

    it('if not found throw an NotFoundException', (done) => {
      jest.spyOn(model, 'findOne').mockReturnValue({
        exec: jest.fn().mockResolvedValueOnce(null) as any,
      } as any);

      service.findById('1').subscribe({
        next: (data) => {
          console.log(data);
        },
        error: (error) => {
          expect(error).toBeDefined();
        },
        complete: done(),
      });
    });
  });

  it('should save post', async () => {
    const toCreated = {
      title: 'test title',
      content: 'test content',
    };

    const toReturned = {
      _id: '5ee49c3115a4e75254bb732e',
      ...toCreated,
    } as Post;

    jest
      .spyOn(model, 'create')
      .mockImplementation(() => Promise.resolve(toReturned));

    const data = await lastValueFrom(service.save(toCreated));
    expect(data._id).toBe('5ee49c3115a4e75254bb732e');
    expect(model.create).toBeCalledWith({
      ...toCreated,
      createdBy: {
        _id: 'dummyId',
      },
    });
    expect(model.create).toBeCalledTimes(1);
  });

  describe('update', () => {
    it('perform update if post exists', (done) => {
      const toUpdated = {
        _id: '5ee49c3115a4e75254bb732e',
        title: 'test title',
        content: 'test content',
      };

      jest.spyOn(model, 'findOneAndUpdate').mockReturnValue({
        exec: jest.fn().mockResolvedValue(toUpdated) as any,
      } as any);

      service.update('5ee49c3115a4e75254bb732e', toUpdated).subscribe({
        next: (data) => {
          expect(data).toBeTruthy();
          expect(model.findOneAndUpdate).toBeCalled();
        },
        error: (error) => console.log(error),
        complete: done(),
      });
    });

    it('throw an NotFoundException if post not exists', (done) => {
      const toUpdated = {
        _id: '5ee49c3115a4e75254bb732e',
        title: 'test title',
        content: 'test content',
      };
      jest.spyOn(model, 'findOneAndUpdate').mockReturnValue({
        exec: jest.fn().mockResolvedValue(null) as any,
      } as any);

      service.update('5ee49c3115a4e75254bb732e', toUpdated).subscribe({
        error: (error) => {
          expect(error).toBeDefined();
          expect(model.findOneAndUpdate).toHaveBeenCalledTimes(1);
        },
        complete: done(),
      });
    });
  });

  describe('delete', () => {
    it('perform delete if post exists', (done) => {
      const toDeleted = {
        _id: '5ee49c3115a4e75254bb732e',
        title: 'test title',
        content: 'test content',
      };
      jest.spyOn(model, 'findOneAndDelete').mockReturnValue({
        exec: jest.fn().mockResolvedValueOnce(toDeleted),
      } as any);

      service.deleteById('anystring').subscribe({
        next: (data) => {
          expect(data).toBeTruthy();
          expect(model.findOneAndDelete).toBeCalled();
        },
        error: (error) => console.log(error),
        complete: done(),
      });
    });

    it('throw an NotFoundException if post not exists', (done) => {
      jest.spyOn(model, 'findOneAndDelete').mockReturnValue({
        exec: jest.fn().mockResolvedValue(null),
      } as any);
      service.deleteById('anystring').subscribe({
        error: (error) => {
          expect(error).toBeDefined();
          expect(model.findOneAndDelete).toBeCalledTimes(1);
        },
        complete: done(),
      });
    });
  });

  it('should delete all post', (done) => {
    jest.spyOn(model, 'deleteMany').mockReturnValue({
      exec: jest.fn().mockResolvedValueOnce({
        deletedCount: 1,
      }),
    } as any);

    service.deleteAll().subscribe({
      next: (data) => expect(data).toBeTruthy,
      error: (error) => console.log(error),
      complete: done(),
    });
  });

  it('should create comment ', async () => {
    const comment = { content: 'test' };
    jest.spyOn(commentModel, 'create').mockImplementation(() =>
      Promise.resolve({
        ...comment,
        post: { _id: 'test' },
      } as any),
    );

    const result = await lastValueFrom(
      service.createCommentFor('test', comment),
    );
    expect(result.content).toEqual('test');
    expect(commentModel.create).toBeCalledWith({
      ...comment,
      post: { _id: 'test' },
      createdBy: { _id: 'dummyId' },
    });
  });

  it('should get comments of post ', async () => {
    jest
      .spyOn(commentModel, 'find')
      .mockImplementation(
        (
          conditions: FilterQuery<Comment>,
          callback?: (err: any, res: Comment[]) => void,
        ) => {
          return {
            select: jest.fn().mockReturnValue({
              exec: jest.fn().mockResolvedValue([
                {
                  _id: 'test',
                  content: 'content',
                  post: { _id: '_test_id' },
                },
              ] as any),
            }),
          } as any;
        },
      );

    const result = await lastValueFrom(service.commentsOf('test'));
    expect(result.length).toBe(1);
    expect(result[0].content).toEqual('content');
    expect(commentModel.find).toBeCalledWith({ post: { _id: 'test' } });
  });
});
Example #14
Source File: post.service.ts    From nestjs-rest-sample with GNU General Public License v3.0 4 votes vote down vote up
@Injectable({ scope: Scope.REQUEST })
export class PostService {
  constructor(
    @Inject(POST_MODEL) private postModel: Model<Post>,
    @Inject(COMMENT_MODEL) private commentModel: Model<Comment>,
    @Inject(REQUEST) private req: AuthenticatedRequest,
  ) { }

  findAll(keyword?: string, skip = 0, limit = 10): Observable<Post[]> {
    if (keyword) {
      return from(
        this.postModel
          .find({ title: { $regex: '.*' + keyword + '.*' } })
          .skip(skip)
          .limit(limit)
          .exec(),
      );
    } else {
      return from(this.postModel.find({}).skip(skip).limit(limit).exec());
    }
  }

  findById(id: string): Observable<Post> {
    return from(this.postModel.findOne({ _id: id }).exec()).pipe(
      mergeMap((p) => (p ? of(p) : EMPTY)),
      throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
    );
  }

  save(data: CreatePostDto): Observable<Post> {
    //console.log('req.user:'+JSON.stringify(this.req.user));
    const createPost: Promise<Post> = this.postModel.create({
      ...data,
      createdBy: { _id: this.req.user.id },
    });
    return from(createPost);
  }

  update(id: string, data: UpdatePostDto): Observable<Post> {
    return from(
      this.postModel
        .findOneAndUpdate(
          { _id: id },
          { ...data, updatedBy: { _id: this.req.user.id } },
          { new: true },
        )
        .exec(),
    ).pipe(
      mergeMap((p) => (p ? of(p) : EMPTY)),
      throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
    );
    // const filter = { _id: id };
    // const update = { ...data, updatedBy: { _id: this.req.user.id } };
    // return from(this.postModel.findOne(filter).exec()).pipe(
    //   mergeMap((post) => (post ? of(post) : EMPTY)),
    //   throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
    //   switchMap((p, i) => {
    //     return from(this.postModel.updateOne(filter, update).exec());
    //   }),
    //   map((res) => res.nModified),
    // );
  }

  deleteById(id: string): Observable<Post> {
    return from(this.postModel.findOneAndDelete({ _id: id }).exec()).pipe(
      mergeMap((p) => (p ? of(p) : EMPTY)),
      throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
    );
    // const filter = { _id: id };
    // return from(this.postModel.findOne(filter).exec()).pipe(
    //   mergeMap((post) => (post ? of(post) : EMPTY)),
    //   throwIfEmpty(() => new NotFoundException(`post:$id was not found`)),
    //   switchMap((p, i) => {
    //     return from(this.postModel.deleteOne(filter).exec());
    //   }),
    //   map((res) => res.deletedCount),
    // );
  }

  deleteAll(): Observable<any> {
    return from(this.postModel.deleteMany({}).exec());
  }

  //  actions for comments
  createCommentFor(id: string, data: CreateCommentDto): Observable<Comment> {
    const createdComment: Promise<Comment> = this.commentModel.create({
      post: { _id: id },
      ...data,
      createdBy: { _id: this.req.user.id },
    });
    return from(createdComment);
  }

  commentsOf(id: string): Observable<Comment[]> {
    const comments = this.commentModel
      .find({
        post: { _id: id },
      })
      .select('-post')
      .exec();
    return from(comments);
  }
}
Example #15
Source File: joi.pipe.ts    From nestjs-joi with MIT License 4 votes vote down vote up
@Injectable({ scope: Scope.REQUEST })
export class JoiPipe implements PipeTransform {
  private readonly schema?: Joi.Schema;
  private readonly type?: Constructor;
  private readonly method?: string;
  private readonly pipeOpts: ValidatedJoiPipeOptions;

  constructor();
  constructor(pipeOpts?: JoiPipeOptions);
  constructor(type: Constructor, pipeOpts?: JoiPipeOptions);
  constructor(schema: Joi.Schema, pipeOpts?: JoiPipeOptions);
  constructor(
    @Inject(REQUEST) private readonly arg?: unknown,
    @Optional() @Inject(JOIPIPE_OPTIONS) pipeOpts?: JoiPipeOptions,
  ) {
    if (arg) {
      // Test for an actual request object, which indicates we're in "injected" mode.
      // This is the case that requires the most performant handling, which is why it
      // should be the first branch.
      if (isHttpRequest(arg)) {
        this.method = arg.method.toUpperCase();
      } else if (isGraphQlRequest(arg)) {
        // @nestjs/graphql, or rather apollo, only supports GET and POST.
        // To provide a consistent experience without hard to understand behavior
        // such as UPDATE group schemas being ignored, we will NOT set the method.
        // JoiPipe will work for this case, but ignore the method.
        // this.method = arg.req.method.toUpperCase();
      } else {
        // This is the "manually called constructor" case, where performance is
        // (ostensibly) not as big of a concern since manual JoiPipes will be
        // constructed only once at app initialization
        if (Joi.isSchema(arg)) {
          this.schema = arg as Joi.Schema;
        } else if (typeof arg === 'function') {
          this.type = arg as Constructor;
        } else {
          // Options passed as first parameter
          pipeOpts = arg as JoiPipeOptions;
        }
      }
    } else {
      // Called without arguments, do nothing
    }

    this.pipeOpts = this.parseOptions(pipeOpts);
  }

  transform(payload: unknown, metadata: ArgumentMetadata): unknown {
    const schema = this.getSchema(metadata);

    if (!schema) {
      // This happens when a metatype was passed by NestJS and it has no
      // validation decoration.
      return payload;
    }

    return JoiPipe.validate(
      payload,
      schema,
      this.pipeOpts.usePipeValidationException,
      this.pipeOpts.skipErrorFormatting,
      // It is technically impossible for this to be undefined since it is explicitely assigned
      // with a default value in parseOptions(), so it is almost impossible to test.
      /* istanbul ignore next */
      this.pipeOpts.defaultValidationOptions || DEFAULT_JOI_OPTS,
      metadata,
    );
  }

  // Called "validate" and NOT "transform", because that would make it match
  // the interface of a pipe INSTANCE and prevent NestJS from recognizing it as
  // a class constructor instead of an instance.
  private static validate<T>(
    payload: unknown,
    schema: Joi.Schema,
    usePipeValidationException: boolean,
    skipErrorFormatting: boolean,
    validationOptions: Joi.ValidationOptions,
    /* istanbul ignore next */
    metadata: ArgumentMetadata = { type: 'custom' },
  ): T {
    const { error, value } = schema.validate(
      payload,
      // This will always get overridden by whatever options have been specified
      // on the schema itself
      validationOptions,
    );

    if (error) {
      // Fixes #4
      if (Joi.isError(error)) {
        // Provide a special response with reasons
        const reasons = error.details
          .map((detail: { message: string }) => detail.message)
          .join(', ');
        const formattedMessage =
          `Request validation of ${metadata.type} ` +
          (metadata.data ? `item '${metadata.data}' ` : '') +
          `failed, because: ${reasons}`;
        if (usePipeValidationException) {
          throw new JoiPipeValidationException(
            skipErrorFormatting ? error.message : formattedMessage,
          );
        } else {
          throw new BadRequestException(skipErrorFormatting ? error : formattedMessage);
        }
      } else {
        // If error is not a validation error, it is probably a custom error thrown by the schema.
        // Pass it through to allow it to be caught by custom error handlers.
        throw error;
      }
    }

    // Everything is fine
    return value as T;
  }

  /**
   * Efficient validation of pipeOpts
   *
   * @param pipeOpts Pipe options as passed in the constructor
   * @returns Validated options with default values applied
   */
  private parseOptions(pipeOpts?: JoiPipeOptions): ValidatedJoiPipeOptions {
    // Pass type arguments to force type validation of the function arguments.
    pipeOpts = Object.assign<JoiPipeOptions, JoiPipeOptions>(
      {
        ...DEFAULT_JOI_PIPE_OPTS,
      },
      pipeOpts || {},
    );
    // Nested merge of the Joi ValidationOptions.
    // TODO This could probably be combined with the above assignment in a deep merge.
    pipeOpts.defaultValidationOptions = Object.assign<Joi.ValidationOptions, Joi.ValidationOptions>(
      {
        ...DEFAULT_JOI_OPTS,
      },
      pipeOpts.defaultValidationOptions || {},
    );

    const errors: string[] = [];

    const unknownKeys = Object.keys(pipeOpts).filter(k => !JOI_PIPE_OPTS_KEYS.includes(k));
    if (unknownKeys.length) {
      errors.push(`Unknown configuration keys: ${unknownKeys.join(', ')}`);
    }
    if (
      pipeOpts.group &&
      !(typeof pipeOpts.group === 'string' || typeof pipeOpts.group === 'symbol')
    ) {
      errors.push(`'group' must be a string or symbol`);
    }
    if (
      Object.prototype.hasOwnProperty.call(pipeOpts, 'usePipeValidationException') &&
      !(typeof pipeOpts.usePipeValidationException === 'boolean')
    ) {
      errors.push(`'usePipeValidationException' must be a boolean`);
    }
    if (
      Object.prototype.hasOwnProperty.call(pipeOpts, 'skipErrorFormatting') &&
      !(typeof pipeOpts.skipErrorFormatting === 'boolean')
    ) {
      errors.push(`'skipErrorFormatting' must be a boolean`);
    }

    if (errors.length) {
      throw new Error(`Invalid JoiPipeOptions:\n${errors.map(x => `- ${x}`).join('\n')}`);
    }

    return pipeOpts as ValidatedJoiPipeOptions;
  }

  /**
   * Determine what schema to return/construct based on the actual metadata
   * passed for this request.
   *
   * @param metadata
   */
  private getSchema(metadata: ArgumentMetadata): Joi.Schema | undefined {
    // If called in request scope mode, give preference to this mode.
    if (this.method && metadata.metatype) {
      let group: JoiValidationGroup | undefined;
      if (this.method === 'PUT' || this.method === 'PATCH') {
        group = JoiValidationGroups.UPDATE;
      } else if (this.method === 'POST') {
        group = JoiValidationGroups.CREATE;
      }

      return JoiPipe.getTypeSchema(metadata.metatype, { group });
    }

    // Prefer a static schema, if specified
    if (this.schema) {
      return this.schema;
    }

    // Prefer a static model, if specified
    if (this.type) {
      // Don't return "no schema" (undefined) if a type was explicitely specified
      return JoiPipe.getTypeSchema(this.type, { forced: true, group: this.pipeOpts.group });
    }

    // Determine the schema from the passed model
    if (metadata.metatype) {
      return JoiPipe.getTypeSchema(metadata.metatype, { group: this.pipeOpts.group });
    }

    return undefined;
  }

  /**
   * Cache map for already constructed schemas
   */
  private static readonly typeSchemaMap = new Map<unknown, Map<string, Joi.Schema | undefined>>();

  /**
   * Obtain the type schema from getTypeSchema().
   * The result is cached for better performance, using the type and options to
   * construct a cache key.
   *
   * If no properties have been decorated in the whole chain, no schema
   * will be returned so as to not throw when inadvertently "validating" using
   * inbuilt types.
   * Returning a schema can be forced to cover cases when a type has been explicitely
   * passed to JoiPipe.
   *
   * @param type The type (decorated class constructor) to construct a schema from
   * @param options An optional options object
   * @param options.forced Force an empty schema to be returned even if no properties of the type have been decorated. (defaults to false)
   * @param options.group An optional JoiValidationGroup to use during schema construction
   */
  private static getTypeSchema(
    type: Constructor,
    /* istanbul ignore next */
    { forced, group }: { forced?: boolean; group?: JoiValidationGroup } = {},
  ): Joi.Schema | undefined {
    // Do not validate basic inbuilt types. This is fast enough that we don't need
    // to cache the result.
    if (type === String || type === Object || type === Number || type === Array) {
      return;
    }

    const cacheKey = 'forced' + (forced ? '1' : '0') + (group ? 'group' + String(group) : '');
    // Check cache.
    if (this.typeSchemaMap.has(type)) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      if (this.typeSchemaMap.get(type)!.has(cacheKey)) {
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        return this.typeSchemaMap.get(type)!.get(cacheKey);
      }
    } else {
      this.typeSchemaMap.set(type, new Map());
    }

    const typeSchema = getTypeSchema(type, { group });

    // The default behavior when no properties have attached schemas is to not
    // validate, otherwise we'd get errors for types with no decorators not intended
    // to be validated when used as global pipe
    if ((!typeSchema || !Object.keys(typeSchema.describe().keys).length) && !forced) {
      // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
      this.typeSchemaMap.get(type)!.set(cacheKey, undefined);
      return undefined;
    }

    // It is assumed that a validation schema was specified because the input value
    // as such is actually required, so we're calling required() here.
    // This might be subject to change if actual use cases are found.
    const finalSchema = typeSchema.required();

    // Cache value
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    this.typeSchemaMap.get(type)!.set(cacheKey, finalSchema);

    return finalSchema;
  }
}