Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

We take great pride in following best practices while coding so that the end result is consistent, descriptive, and easy to interpret.

Hence building on top of the script you purchase from us is easy. You can always customize things yourself or reach out to us and let us bring your vision to life.

Feel free to check out some example code snippets below.

Expand
titlePerformer Controller
Code Block
import {
  Controller,
  Injectable,
  HttpCode,
  HttpStatus,
  UsePipes,
  ValidationPipe,
  Get,
  Param,
  Query,
  HttpException,
  Put,
  Body,
  UseGuards,
  Request,
  Post
} from '@nestjs/common';
import { omit } from 'lodash';
import {
  DataResponse,
  PageableData
} from 'src/kernel';
import { Roles } from 'src/modules/auth';
import { AuthGuard, RoleGuard } from 'src/modules/auth/guards';
import { ContactPayload } from 'src/modules/contact/payloads';
import { MailerService } from 'src/modules/mailer';
import { PERFORMER_STATUSES, UNALLOWED_SELF_UPDATE_FIELDS } from '../constants';
import {
  PerformerDto,
  IPerformerResponse
} from '../dtos';
import {
  PerformerSearchPayload, PerformerUpdatePayload
} from '../payloads';
import { PerformerService, PerformerSearchService } from '../services';

@Injectable()
@Controller('performers')
export class PerformerController {
  constructor(
    private readonly performerService: PerformerService,
    private readonly performerSearchService: PerformerSearchService,
    private readonly mailService: MailerService
  ) {}

  @Get('/search')
  @HttpCode(HttpStatus.OK)
  @UsePipes(new ValidationPipe({ transform: true }))
  async usearch(
    @Query() req: PerformerSearchPayload
  ): Promise<DataResponse<PageableData<IPerformerResponse>>> {
    req.status = 'active';
    // default sort is score
    // req.sort = 'popular';
    const data = await this.performerSearchService.search(req);
    return DataResponse.ok(data);
  }

  @Get('/me')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UsePipes(new ValidationPipe({ transform: true }))
  async getMyProfile(@Request() req: any): Promise<DataResponse<any>> {
    const { authUser, jwtToken } = req;
    const performer = await this.performerService.findByUserId(
      authUser.sourceId
    );
    const details = await this.performerService.getDetails(performer._id, {
      responseDocument: true,
      jwtToken
    });
    if (details.status === PERFORMER_STATUSES.INACTIVE) {
      throw new HttpException('This account is suspended', 403);
    }
    return DataResponse.ok(details.toResponse(true));
  }

  @Get('/:username')
  @HttpCode(HttpStatus.OK)
  async getDetails(
    @Param('username') performerUsername: string
  ): Promise<DataResponse<Partial<PerformerDto>>> {
    const data = await this.performerService.findByUsername(performerUsername);

    if (data.status !== PERFORMER_STATUSES.ACTIVE) {
      throw new HttpException('This account is suspended', 403);
    }

    return DataResponse.ok(data.toPublicDetailsResponse());
  }

  @Put('/')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UsePipes(new ValidationPipe({ transform: true }))
  async updateMyProfile(
    @Body() payload: PerformerUpdatePayload,
    @Request() req: any
  ): Promise<DataResponse<Partial<PerformerDto>>> {
    const { sourceId } = req.authUser;
    const performer = await this.performerService.findByUserId(sourceId);

    const data = await this.performerService.update(
      performer._id,

      // remove unwanted fields
      omit(payload, UNALLOWED_SELF_UPDATE_FIELDS)
    );

    return DataResponse.ok(data);
  }

  @Get('/:username/related')
  @HttpCode(HttpStatus.OK)
  async getRelated(
    @Param('username') username: string
  ): Promise<DataResponse<any>> {
    const data = await this.performerService.getRelated(username);
    return DataResponse.ok(data);
  }

  @Post('/:username/contact')
  @HttpCode(HttpStatus.OK)
  @UseGuards(AuthGuard)
  async contact(
    @Param('username') username: string,
    @Body() payload: ContactPayload
  ): Promise<DataResponse<any>> {
    const performer = await this.performerService.findByUsername(username);
    if (!performer || performer.status !== 'active') { return DataResponse.error(new Error("Model doesn't exists")); }
    await this.mailService.send({
      subject: `New contact from ${payload.name}`,
      to: performer.email,
      data: payload,
      template: 'contact-performer'
    });
    return DataResponse.ok(true);
  }
}
Expand
titlePerformer Service
Code Block
import {
  Injectable, forwardRef, Inject
} from '@nestjs/common';
import { Model } from 'mongoose';
import {
  EntityNotFoundException, QueueEventService
} from 'src/kernel';
import { ObjectId } from 'mongodb';
import { FileService } from 'src/modules/file/services';
import { FileDto } from 'src/modules/file';
import { UserDto } from 'src/modules/user/dtos';
import { REF_TYPE } from 'src/modules/file/constants';
import { isObjectId } from 'src/kernel/helpers/string.helper';
import { CategoryService } from 'src/modules/category/services';
import { EVENT } from 'src/kernel/constants';
import { UserService } from 'src/modules/user/services';
import { PerformerDto } from '../dtos';
import {
  UsernameExistedException
} from '../exceptions';
import {
  PerformerModel
} from '../models';
import {
  PerformerCreatePayload,
  PerformerUpdatePayload
} from '../payloads';
import { PERFORMER_MODEL_PROVIDER } from '../providers';
import { PERFORMER_CHANNEL } from '../constants';

@Injectable()
export class PerformerService {
  constructor(
    @Inject(forwardRef(() => CategoryService))
    private readonly categoryService: CategoryService,
    @Inject(PERFORMER_MODEL_PROVIDER)
    private readonly Performer: Model<PerformerModel>,
    private readonly fileService: FileService,
    @Inject(forwardRef(() => UserService))
    private readonly userService: UserService,
    private readonly queueEventService: QueueEventService
  ) {}

  public async findById(id: string | ObjectId): Promise<PerformerDto> {
    const model = await this.Performer.findById(id);
    if (!model) return null;
    return new PerformerDto(model);
  }

  public async findByEmail(email: string): Promise<PerformerDto> {
    if (!email) {
      return null;
    }
    const model = await this.Performer.findOne({
      email: email.toLowerCase()
    });
    if (!model) return null;
    return new PerformerDto(model);
  }

  public async create(
    payload: PerformerCreatePayload,
    user?: UserDto
  ): Promise<PerformerDto> {
    const data = {
      ...payload,
      updatedAt: new Date(),
      createdAt: new Date()
    } as any;
    const userNameCheck = await this.Performer.countDocuments({
      username: payload.username.trim()
    });
    if (userNameCheck) {
      throw new UsernameExistedException();
    }
    const usernameCheck2 = await this.userService.findByUsername(
      payload.username.trim()
    );
    if (usernameCheck2) {
      throw new UsernameExistedException();
    }

    if (user) {
      data.createdBy = user._id;
    }
    data.username = data.username.trim();
    data.email = data.email ? data.email.toLowerCase() : null;
    if (!data.name) data.name = data.username;

    const performer = await this.Performer.create(data);

    // update / create auth
    await this.queueEventService.publish({
      channel: PERFORMER_CHANNEL,
      eventName: EVENT.CREATED,
      data: {
        performer: performer.toObject(),
        payload
      }
    });

    return new PerformerDto(performer);
  }

  public async update(
    id: string | ObjectId,
    payload: Partial<PerformerUpdatePayload>
  ): Promise<any> {
    const performer = await this.Performer.findById(id);
    if (!performer) {
      throw new EntityNotFoundException();
    }
    const data = { ...payload, updatedAt: new Date() } as any;
    if (!data.name) {
      data.name = [data.firstName || '', data.lastName || ''].join(' ');
    }
    if (data.username && data.username.trim() !== performer.username) {
      const usernameCheck = await this.Performer.countDocuments({
        username: data.username.trim(),
        _id: { $ne: performer._id }
      });
      if (usernameCheck) {
        throw new UsernameExistedException();
      }
      const usernameCheck2 = await this.userService.findByUsername(
        payload.username.trim()
      );
      if (usernameCheck2._id.toString() !== performer.userId?.toString()) {
        throw new UsernameExistedException();
      }
      data.username = data.username.trim();
    }
    data.categoryIds = data.categoryIds || [];
    if (!data.name) data.name = data.username;
    await this.Performer.updateOne({ _id: id }, data);

    // update / create auth
    await this.queueEventService.publish({
      channel: PERFORMER_CHANNEL,
      eventName: EVENT.UPDATED,
      data: {
        performer: performer.toObject(),
        payload
      }
    });
    return { updated: true };
  }

  public async updateAvatar(performer: PerformerDto, file: FileDto) {
    await this.Performer.updateOne(
      { _id: performer._id },
      {
        avatarId: file._id,
        avatarPath: file.path
      }
    );
    await this.fileService.addRef(file._id, {
      itemId: performer._id,
      itemType: REF_TYPE.PERFORMER
    });

    const p = await this.Performer.findById(performer._id);

    await this.queueEventService.publish({
      channel: PERFORMER_CHANNEL,
      eventName: 'avatarUpdated',
      data: {
        performer: p.toObject(),
        file
      }
    });

    return file;
  }

  public async updateWelcomeVideo(user: PerformerDto, file: FileDto) {
    await this.Performer.updateOne(
      { _id: user._id },
      {
        welcomeVideoId: file._id,
        welcomeVideoPath: file.path
      }
    );

    await this.fileService.addRef(file._id, {
      itemId: user._id,
      itemType: REF_TYPE.PERFORMER
    });

    return file;
  }

  public async findByUserId(userId: string | ObjectId) {
    return this.Performer.findOne({ userId });
  }

  public async calculateScore(performerId) {
    const performer = performerId instanceof this.Performer
      ? performerId
      : await this.Performer.findById(performerId);
    if (!performer) return 0;

    let score = 0;
    if (performer.vip) score += 1;
    if (performer.verified) score += 1;
    return score;
  }
}
Aura divider
paramsJTdCJTIydHlwZSUyMiUzQSUyMnRleHQlMjIlMkMlMjJzZXJpYWxpemVkU3R5bGVzJTIyJTNBJTIyJTdCJTVDJTIyYWxpZ25tZW50JTVDJTIyJTNBJTdCJTVDJTIyaG9yaXpvbnRhbCU1QyUyMiUzQSU1QyUyMmNlbnRlciU1QyUyMiU3RCUyQyU1QyUyMmljb24lNUMlMjIlM0ElN0IlNUMlMjJuYW1lJTVDJTIyJTNBJTVDJTIyZmFQYXBlclBsYW5lJTVDJTIyJTJDJTVDJTIyY29sb3IlNUMlMjIlM0ElNUMlMjIlMjMzMzMlNUMlMjIlMkMlNUMlMjJzaXplJTVDJTIyJTNBMjQlN0QlMkMlNUMlMjJ0ZXh0JTVDJTIyJTNBJTdCJTVDJTIyY29sb3IlNUMlMjIlM0ElNUMlMjIlMjMwMDAwMDBhYiU1QyUyMiUyQyU1QyUyMmZvbnRTaXplJTVDJTIyJTNBMTglMkMlNUMlMjJ0ZXh0QWxpZ24lNUMlMjIlM0ElNUMlMjJsZWZ0JTVDJTIyJTJDJTVDJTIyZm9udFdlaWdodCU1QyUyMiUzQSU1QyUyMmJvbGQlNUMlMjIlMkMlNUMlMjJ0ZXh0JTVDJTIyJTNBJTVDJTIyQXJlJTIweW91JTIwbG9zdCUzRiU1QyUyMiU3RCUyQyU1QyUyMmJvcmRlciU1QyUyMiUzQSU3QiU1QyUyMnRvcCU1QyUyMiUzQWZhbHNlJTJDJTVDJTIycmlnaHQlNUMlMjIlM0FmYWxzZSUyQyU1QyUyMmJvdHRvbSU1QyUyMiUzQXRydWUlMkMlNUMlMjJsZWZ0JTVDJTIyJTNBZmFsc2UlMkMlNUMlMjJjb2xvciU1QyUyMiUzQSU1QyUyMiUyMzI2ODRmZiU1QyUyMiUyQyU1QyUyMnN0eWxlJTVDJTIyJTNBJTVDJTIyZG90dGVkJTVDJTIyJTJDJTVDJTIyd2lkdGglNUMlMjIlM0E0JTdEJTJDJTVDJTIyc2l6ZSU1QyUyMiUzQSU3QiU3RCU3RCUyMiU3RA==

Aura inline button
paramsJTdCJTIybGFiZWwlMjIlM0ElMjJHbyUyMHRvJTIweFNjb3J0cyUyME5hdmlnYXRvciUyMiUyQyUyMnNpemUlMjIlM0ElMjJtZWRpdW0lMjIlMkMlMjJzaGFwZSUyMiUzQSUyMm1peGVkJTIyJTJDJTIyc3RhdGVzJTIyJTNBJTdCJTIyaWRsZSUyMiUzQSU3QiUyMmNvbG9ycyUyMiUzQSU3QiUyMmJhY2tncm91bmQlMjIlM0ElMjIlMjMyNjg0ZmYlMjIlMkMlMjJsYWJlbCUyMiUzQSUyMiUyM2ZmZmZmZiUyMiUyQyUyMm91dGxpbmUlMjIlM0ElMjIlMjNmZmZmZmYlMjIlN0QlMkMlMjJzaGFkb3clMjIlM0ElMjJlMjAwJTIyJTdEJTJDJTIyaG92ZXIlMjIlM0ElN0IlMjJjb2xvcnMlMjIlM0ElN0IlMjJiYWNrZ3JvdW5kJTIyJTNBJTIyJTIzZmZmZmZmJTIyJTJDJTIybGFiZWwlMjIlM0ElMjIlMjMwMDAwMDBhYiUyMiUyQyUyMm91dGxpbmUlMjIlM0ElMjIlMjMwMDAwMDBhYiUyMiU3RCUyQyUyMnNoYWRvdyUyMiUzQSUyMmUyMDAlMjIlN0QlN0QlMkMlMjJsaW5rJTIyJTNBJTdCJTIydmFsdWUlMjIlM0ElMjIyNDU3NjAxJTIyJTJDJTIydHlwZSUyMiUzQSUyMnBhZ2UlMjIlMkMlMjJ0YXJnZXQlMjIlM0ElMjJfc2VsZiUyMiU3RCUyQyUyMmljb24lMjIlM0ElN0IlMjJwb3NpdGlvbiUyMiUzQSUyMmxlZnQlMjIlMkMlMjJpY29uJTIyJTNBJTIyZmEtY29tcGFzcyUyMiU3RCU3RA==
Aura inline button
paramsJTdCJTIybGFiZWwlMjIlM0ElMjJHbyUyMHRvJTIweFNjb3J0cyUyME5hdmlnYXRvciUyMiUyQyUyMnNpemUlMjIlM0ElMjJtZWRpdW0lMjIlMkMlMjJzaGFwZSUyMiUzQSUyMm1peGVkJTIyJTJDJTIyc3RhdGVzJTIyJTNBJTdCJTIyaWRsZSUyMiUzQSU3QiUyMmNvbG9ycyUyMiUzQSU3QiUyMmJhY2tncm91bmQlMjIlM0ElMjIlMjMyNjg0ZmYlMjIlMkMlMjJsYWJlbCUyMiUzQSUyMiUyM2ZmZmZmZiUyMiUyQyUyMm91dGxpbmUlMjIlM0ElMjIlMjNmZmZmZmYlMjIlN0QlMkMlMjJzaGFkb3clMjIlM0ElMjJlMjAwJTIyJTdEJTJDJTIyaG92ZXIlMjIlM0ElN0IlMjJjb2xvcnMlMjIlM0ElN0IlMjJiYWNrZ3JvdW5kJTIyJTNBJTIyJTIzZmZmZmZmJTIyJTJDJTIybGFiZWwlMjIlM0ElMjIlMjMwMDAwMDBhYiUyMiUyQyUyMm91dGxpbmUlMjIlM0ElMjIlMjMwMDAwMDBhYiUyMiU3RCUyQyUyMnNoYWRvdyUyMiUzQSUyMmUyMDAlMjIlN0QlN0QlMkMlMjJsaW5rJTIyJTNBJTdCJTIydmFsdWUlMjIlM0ElMjIyNDU3NjAxJTIyJTJDJTIydHlwZSUyMiUzQSUyMnBhZ2UlMjIlMkMlMjJ0YXJnZXQlMjIlM0ElMjJfc2VsZiUyMiU3RCUyQyUyMmljb24lMjIlM0ElN0IlMjJwb3NpdGlvbiUyMiUzQSUyMmxlZnQlMjIlMkMlMjJpY29uJTIyJTNBJTIyZmEtY29tcGFzcyUyMiU3RCU3RA==JTdCJTIybGFiZWwlMjIlM0ElMjJUZWNoJTIwRG9jcyUyMGZvciUyMG90aGVyJTIwcHJvZHVjdHMlMjIlMkMlMjJzaXplJTIyJTNBJTIybWVkaXVtJTIyJTJDJTIyc2hhcGUlMjIlM0ElMjJtaXhlZCUyMiUyQyUyMnN0YXRlcyUyMiUzQSU3QiUyMmlkbGUlMjIlM0ElN0IlMjJjb2xvcnMlMjIlM0ElN0IlMjJiYWNrZ3JvdW5kJTIyJTNBJTIyJTIzMjY4NGZmJTIyJTJDJTIybGFiZWwlMjIlM0ElMjIlMjNmZmZmZmYlMjIlMkMlMjJvdXRsaW5lJTIyJTNBJTIyJTIzZmZmZmZmJTIyJTdEJTJDJTIyc2hhZG93JTIyJTNBJTIyZTIwMCUyMiU3RCUyQyUyMmhvdmVyJTIyJTNBJTdCJTIyY29sb3JzJTIyJTNBJTdCJTIyYmFja2dyb3VuZCUyMiUzQSUyMiUyM2ZmZmZmZiUyMiUyQyUyMmxhYmVsJTIyJTNBJTIyJTIzMDAwMDAwYWIlMjIlMkMlMjJvdXRsaW5lJTIyJTNBJTIyJTIzMDAwMDAwYWIlMjIlN0QlMkMlMjJzaGFkb3clMjIlM0ElMjJlMjAwJTIyJTdEJTdEJTJDJTIybGluayUyMiUzQSU3QiUyMnZhbHVlJTIyJTNBJTIyMTk2NzU1JTIyJTJDJTIydHlwZSUyMiUzQSUyMnBhZ2UlMjIlMkMlMjJ0YXJnZXQlMjIlM0ElMjJfc2VsZiUyMiU3RCUyQyUyMmljb24lMjIlM0ElN0IlMjJwb3NpdGlvbiUyMiUzQSUyMmxlZnQlMjIlMkMlMjJpY29uJTIyJTNBJTIyZmEtcXJjb2RlJTIyJTdEJTdE
Aura inline button
paramsJTdCJTIybGFiZWwlMjIlM0ElMjJDb25uZWN0JTIwd2l0aCUyMHVzJTIyJTJDJTIyc2l6ZSUyMiUzQSUyMm1lZGl1bSUyMiUyQyUyMnNoYXBlJTIyJTNBJTIybWl4ZWQlMjIlMkMlMjJzdGF0ZXMlMjIlM0ElN0IlMjJpZGxlJTIyJTNBJTdCJTIyY29sb3JzJTIyJTNBJTdCJTIyYmFja2dyb3VuZCUyMiUzQSUyMiUyMzI2ODRmZiUyMiUyQyUyMmxhYmVsJTIyJTNBJTIyJTIzZmZmZmZmJTIyJTJDJTIyb3V0bGluZSUyMiUzQSUyMiUyM2ZmZmZmZiUyMiU3RCUyQyUyMnNoYWRvdyUyMiUzQSUyMmUyMDAlMjIlN0QlMkMlMjJob3ZlciUyMiUzQSU3QiUyMmNvbG9ycyUyMiUzQSU3QiUyMmJhY2tncm91bmQlMjIlM0ElMjIlMjNmZmZmZmYlMjIlMkMlMjJsYWJlbCUyMiUzQSUyMiUyMzAwMDAwMGFiJTIyJTJDJTIyb3V0bGluZSUyMiUzQSUyMiUyMzAwMDAwMGFiJTIyJTdEJTJDJTIyc2hhZG93JTIyJTNBJTIyZTIwMCUyMiU3RCU3RCUyQyUyMmxpbmslMjIlM0ElN0IlMjJ0eXBlJTIyJTNBJTIybGluayUyMiUyQyUyMnZhbHVlJTIyJTNBJTIyaHR0cHMlM0ElMkYlMkZhZGVudC5pbyUyRmNvbnRhY3QlMjIlMkMlMjJ0YXJnZXQlMjIlM0ElMjJfYmxhbmslMjIlN0QlMkMlMjJpY29uJTIyJTNBJTdCJTIycG9zaXRpb24lMjIlM0ElMjJsZWZ0JTIyJTJDJTIyaWNvbiUyMiUzQSUyMmZhLWhlYWRwaG9uZXMtYWx0JTIyJTdEJTdE