Code Snippets

Performer Controller Code Samples:

import { Controller, Injectable, UseGuards, Body, Post, HttpCode, HttpStatus, UsePipes, ValidationPipe, Put, Get, Param, Query, Request, UseInterceptors, HttpException } from '@nestjs/common'; import { DataResponse, PageableData, getConfig, ForbiddenException } from 'src/kernel'; import { AuthService, Roles } from 'src/modules/auth'; import { RoleGuard, LoadUser } from 'src/modules/auth/guards'; import { CurrentUser } from 'src/modules/auth/decorators'; import { FileUploadInterceptor, FileUploaded, FileDto } from 'src/modules/file'; import { REF_TYPE } from 'src/modules/file/constants'; import { FileService } from 'src/modules/file/services'; import { UserDto } from 'src/modules/user/dtos'; import { CountryService } from 'src/modules/utils/services'; import { PERFORMER_STATUSES } from '../constants'; import { PerformerDto, IPerformerResponse } from '../dtos'; import { SelfUpdatePayload, PerformerSearchPayload, BankingSettingPayload, PaymentGatewaySettingPayload } from '../payloads'; import { PerformerService, PerformerSearchService } from '../services'; @Injectable() @Controller('performers') export class PerformerController { constructor( private readonly performerService: PerformerService, private readonly performerSearchService: PerformerSearchService, private readonly authService: AuthService, private readonly countryService: CountryService, private readonly fileService: FileService ) {} @Get('/me') @HttpCode(HttpStatus.OK) @Roles('performer') @UseGuards(RoleGuard) async me(@Request() req: any): Promise<DataResponse<IPerformerResponse>> { const user = await this.performerService.getDetails(req.user._id, req.jwToken); return DataResponse.ok(new PerformerDto(user).toResponse(true, false)); } @Get('/search') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) async usearch( @Query() req: PerformerSearchPayload ): Promise<DataResponse<PageableData<IPerformerResponse>>> { const data = await this.performerSearchService.search(req); return DataResponse.ok({ total: data.total, data: data.data.map((p) => p.toPublicDetailsResponse()) }); } @Get('/top') @HttpCode(HttpStatus.OK) @UsePipes(new ValidationPipe({ transform: true })) async topPerformers( @Query() req: PerformerSearchPayload ): Promise<DataResponse<PageableData<IPerformerResponse>>> { const query = { ...req }; // only query activated performer, sort by online time query.status = PERFORMER_STATUSES.ACTIVE; const data = await this.performerSearchService.topPerformers(query); return DataResponse.ok({ total: data.total, data: data.data.map((p) => p.toSearchResponse()) }); } @Put('/:id') @Roles('performer') @UseGuards(RoleGuard) async updateUser( @Body() payload: SelfUpdatePayload, @Param('id') performerId: string, @Request() req: any ): Promise<DataResponse<IPerformerResponse>> { await this.performerService.selfUpdate(performerId, payload); const performer = await this.performerService.getDetails(performerId, req.jwToken); if (payload.password) { await Promise.all([ performer.email && this.authService.create({ source: 'performer', sourceId: performer._id, type: 'email', key: performer.email, value: payload.password }), performer.username && this.authService.create({ source: 'performer', sourceId: performer._id, type: 'username', key: performer.username, value: payload.password }) ]); } return DataResponse.ok(new PerformerDto(performer).toResponse(true, false)); } @Get('/:username') @UseGuards(LoadUser) @HttpCode(HttpStatus.OK) async getDetails( @Param('username') performerUsername: string, @Request() req: any, @CurrentUser() user: UserDto ): Promise<DataResponse<Partial<PerformerDto>>> { let ipClient = req.headers['x-forwarded-for'] || req.connection.remoteAddress; if (ipClient.substr(0, 7) === '::ffff:') { ipClient = ipClient.substr(7); } // const ipClient = '115.75.211.252'; const whiteListIps = ['127.0.0.1', '0.0.0.1']; let countryCode = null; if (whiteListIps.indexOf(ipClient) === -1) { const userCountry = await this.countryService.findCountryByIP(ipClient) as any; if (userCountry?.status === 'success' && userCountry?.countryCode) { countryCode = userCountry.countryCode; } } const performer = await this.performerService.findByUsername( performerUsername, countryCode, user ); if (!performer || performer.status !== PERFORMER_STATUSES.ACTIVE) { throw new HttpException('This account is suspended', 422); } return DataResponse.ok(performer.toPublicDetailsResponse()); } @Post('/documents/upload') @Roles('performer') @UseGuards(RoleGuard) @HttpCode(HttpStatus.OK) @UseInterceptors( FileUploadInterceptor('performer-document', 'file', { destination: getConfig('file').documentDir }) ) async uploadPerformerDocument( @CurrentUser() currentUser: UserDto, @FileUploaded() file: FileDto, @Request() req: any ): Promise<any> { await this.fileService.addRef(file._id, { itemId: currentUser._id, itemType: REF_TYPE.PERFORMER }); return DataResponse.ok({ ...file, url: `${file.getUrl()}?documentId=${file._id}&token=${req.jwToken}` }); } @Put('/:id/payment-gateway-settings') @HttpCode(HttpStatus.OK) @Roles('performer') @UseGuards(RoleGuard) async updatePaymentGatewaySetting( @Body() payload: PaymentGatewaySettingPayload ) { const data = await this.performerService.updatePaymentGateway(payload); return DataResponse.ok(data); } @Post('/avatar/upload') @HttpCode(HttpStatus.OK) @Roles('performer') @UseGuards(RoleGuard) @UseInterceptors( FileUploadInterceptor('avatar', 'avatar', { destination: getConfig('file').avatarDir, generateThumbnail: true, replaceByThumbnail: true, thumbnailSize: getConfig('image').avatar }) ) async uploadPerformerAvatar( @FileUploaded() file: FileDto, @CurrentUser() performer: PerformerDto ): Promise<any> { // TODO - define url for perfomer id if have? await this.performerService.updateAvatar(performer, file); return DataResponse.ok({ ...file, url: file.getUrl() }); } @Post('/cover/upload') @HttpCode(HttpStatus.OK) @Roles('performer') @UseGuards(RoleGuard) @UseInterceptors( FileUploadInterceptor('cover', 'cover', { destination: getConfig('file').coverDir, generateThumbnail: true, thumbnailSize: getConfig('image').coverThumbnail }) ) async uploadPerformerCover( @FileUploaded() file: FileDto, @CurrentUser() performer: PerformerDto ): Promise<any> { // TODO - define url for perfomer id if have? await this.performerService.updateCover(performer, file); return DataResponse.ok({ ...file, url: file.getUrl() }); } @Post('/welcome-video/upload') @HttpCode(HttpStatus.OK) @Roles('performer') @UseGuards(RoleGuard) @UseInterceptors( FileUploadInterceptor('performer-welcome-video', 'welcome-video', { destination: getConfig('file').videoDir }) ) async uploadPerformerVideo( @FileUploaded() file: FileDto, @CurrentUser() performer: PerformerDto ): Promise<any> { // TODO - define url for perfomer id if have? await this.performerService.updateWelcomeVideo(performer, file); return DataResponse.ok({ ...file, url: file.getUrl() }); } @Put('/:id/banking-settings') @HttpCode(HttpStatus.OK) @Roles('performer') @UseGuards(RoleGuard) async updateBankingSetting( @Param('id') performerId: string, @Body() payload: BankingSettingPayload, @CurrentUser() user: UserDto ) { const data = await this.performerService.updateBankingSetting( performerId, payload, user ); return DataResponse.ok(data); } @Get('/documents/auth/check') @HttpCode(HttpStatus.OK) async checkAuth( @Request() req: any ) { if (!req.query.token) throw new ForbiddenException(); const user = await this.authService.getSourceFromJWT(req.query.token); if (!user) { throw new ForbiddenException(); } const valid = await this.performerService.checkAuthDocument(req, user); return DataResponse.ok(valid); } }

Performer Service Code Samples:

import { Injectable, Inject, NotAcceptableException, forwardRef, HttpException } from '@nestjs/common'; import { Model } from 'mongoose'; import { EntityNotFoundException, ForbiddenException, QueueEventService, QueueEvent, StringHelper } from 'src/kernel'; import { ObjectId } from 'mongodb'; import { FileService } from 'src/modules/file/services'; import { SettingService } from 'src/modules/settings'; import { SETTING_KEYS } from 'src/modules/settings/constants'; import { SubscriptionService } from 'src/modules/subscription/services/subscription.service'; import { FileDto } from 'src/modules/file'; import { UserDto } from 'src/modules/user/dtos'; import { AuthService } from 'src/modules/auth/services'; import { EVENT } from 'src/kernel/constants'; import { REF_TYPE } from 'src/modules/file/constants'; import { EmailHasBeenTakenException } from 'src/modules/user/exceptions'; import { MailerService } from 'src/modules/mailer'; import { UserService } from 'src/modules/user/services'; import { isObjectId } from 'src/kernel/helpers/string.helper'; import { PerformerBlockService } from 'src/modules/block/services'; import { PERFORMER_UPDATE_STATUS_CHANNEL, DELETE_PERFORMER_CHANNEL } from '../constants'; import { PerformerDto } from '../dtos'; import { UsernameExistedException, EmailExistedException } from '../exceptions'; import { PerformerModel, PaymentGatewaySettingModel, CommissionSettingModel, BankingModel } from '../models'; import { PerformerCreatePayload, PerformerUpdatePayload, PerformerRegisterPayload, SelfUpdatePayload, PaymentGatewaySettingPayload, CommissionSettingPayload, BankingSettingPayload } from '../payloads'; import { PERFORMER_BANKING_SETTING_MODEL_PROVIDER, PERFORMER_COMMISSION_SETTING_MODEL_PROVIDER, PERFORMER_MODEL_PROVIDER, PERFORMER_PAYMENT_GATEWAY_SETTING_MODEL_PROVIDER } from '../providers'; @Injectable() export class PerformerService { constructor( @Inject(forwardRef(() => PerformerBlockService)) private readonly performerBlockService: PerformerBlockService, @Inject(forwardRef(() => AuthService)) private readonly authService: AuthService, @Inject(forwardRef(() => UserService)) private readonly userService: UserService, @Inject(forwardRef(() => FileService)) private readonly fileService: FileService, @Inject(forwardRef(() => SubscriptionService)) private readonly subscriptionService: SubscriptionService, @Inject(PERFORMER_MODEL_PROVIDER) private readonly performerModel: Model<PerformerModel>, private readonly queueEventService: QueueEventService, private readonly mailService: MailerService, @Inject(PERFORMER_PAYMENT_GATEWAY_SETTING_MODEL_PROVIDER) private readonly paymentGatewaySettingModel: Model<PaymentGatewaySettingModel>, @Inject(PERFORMER_BANKING_SETTING_MODEL_PROVIDER) private readonly bankingSettingModel: Model<BankingModel>, @Inject(PERFORMER_COMMISSION_SETTING_MODEL_PROVIDER) private readonly commissionSettingModel: Model<CommissionSettingModel> ) { } public async findById( id: string | ObjectId ): Promise<PerformerModel> { const model = await this.performerModel.findById(id); if (!model) return null; return model; } public async findOne(query) { const data = await this.performerModel.findOne(query).lean(); return data; } public async getBankingSettings(performerId: ObjectId) { return this.bankingSettingModel.findOne({ performerId }); } public async checkExistedEmailorUsername(payload) { const data = payload.username ? await this.performerModel.countDocuments({ username: payload.username.trim().toLowerCase() }) : await this.performerModel.countDocuments({ email: payload.email.toLowerCase() }); return data; } public async findByUsername( username: string, countryCode?: string, currentUser?: UserDto ): Promise<PerformerDto> { const query = isObjectId(username) ? { _id: username } : { username: username.trim() }; const model = await this.performerModel.findOne(query); if (!model) throw new EntityNotFoundException(); let isBlocked = false; let isSubscribed = false; if (countryCode && `${currentUser?._id}` !== `${model._id}`) { isBlocked = await this.performerBlockService.checkBlockedCountryByIp(model._id, countryCode); if (isBlocked) { throw new HttpException('Your country has been blocked by this model', 403); } } if (currentUser && currentUser._id) { isBlocked = `${currentUser?._id}` !== `${model._id}` && await this.performerBlockService.checkBlockedByPerformer( model._id, currentUser._id ); if (isBlocked) throw new HttpException('You have been blocked by this model', 403); const checkSubscribe = await this.subscriptionService.checkSubscribed(model._id, currentUser._id); isSubscribed = !!checkSubscribe; } const dto = new PerformerDto(model); dto.isSubscribed = isSubscribed; if (model.avatarId) { const avatar = await this.fileService.findById(model.avatarId); dto.avatarPath = avatar ? avatar.path : null; } if (model.welcomeVideoId) { const welcomeVideo = await this.fileService.findById( model.welcomeVideoId ); dto.welcomeVideoPath = welcomeVideo ? welcomeVideo.getUrl() : null; } await this.viewProfile(model._id); return dto; } public async findByEmail(email: string): Promise<PerformerDto> { if (!email) { return null; } const model = await this.performerModel.findOne({ email: email.toLowerCase() }); if (!model) return null; return new PerformerDto(model); } public async delete(id: string) { if (!StringHelper.isObjectId(id)) throw new ForbiddenException(); const performer = await this.performerModel.findById(id); if (!performer) throw new EntityNotFoundException(); await this.performerModel.deleteOne({ _id: id }); await this.queueEventService.publish(new QueueEvent({ channel: DELETE_PERFORMER_CHANNEL, eventName: EVENT.DELETED, data: new PerformerDto(performer).toResponse() })); return { deleted: true }; } public async create( payload: PerformerCreatePayload, user?: UserDto ): Promise<PerformerDto> { const data = { ...payload, updatedAt: new Date(), createdAt: new Date() } as any; const countPerformerUsername = await this.performerModel.countDocuments({ username: payload.username.trim().toLowerCase() }); const countUserUsername = await this.userService.checkExistedEmailorUsername({ username: payload.username }); if (countPerformerUsername || countUserUsername) { throw new UsernameExistedException(); } const countPerformerEmail = await this.performerModel.countDocuments({ email: payload.email.toLowerCase() }); const countUserEmail = await this.userService.checkExistedEmailorUsername({ email: payload.email }); if (countPerformerEmail || countUserEmail) { throw new EmailExistedException(); } if (payload.avatarId) { const avatar = await this.fileService.findById(payload.avatarId); if (!avatar) { throw new EntityNotFoundException('Avatar not found!'); } // TODO - check for other storaged data.avatarPath = avatar.path; } if (payload.coverId) { const cover = await this.fileService.findById(payload.coverId); if (!cover) { throw new EntityNotFoundException('Cover not found!'); } // TODO - check for other storaged data.coverPath = cover.path; } // TODO - check for category Id, studio if (user) { data.createdBy = user._id; } data.username = data.username.trim().toLowerCase(); data.email = data.email.toLowerCase(); if (data.dateOfBirth) { data.dateOfBirth = new Date(data.dateOfBirth); } if (!data.name) { data.name = data.firstName && data.lastName ? [data.firstName, data.lastName].join(' ') : 'No_display_name'; } const performer = await this.performerModel.create(data); await Promise.all([ payload.idVerificationId && this.fileService.addRef(payload.idVerificationId, { itemId: performer._id as any, itemType: REF_TYPE.PERFORMER }), payload.documentVerificationId && this.fileService.addRef(payload.documentVerificationId, { itemId: performer._id as any, itemType: REF_TYPE.PERFORMER }), payload.avatarId && this.fileService.addRef(payload.avatarId, { itemId: performer._id as any, itemType: REF_TYPE.PERFORMER }) ]); // TODO - fire event? return new PerformerDto(performer); } public async register( payload: PerformerRegisterPayload ): Promise<PerformerDto> { const data = { ...payload, updatedAt: new Date(), createdAt: new Date() } as any; const countPerformerUsername = await this.performerModel.countDocuments({ username: payload.username.trim().toLowerCase() }); const countUserUsername = await this.userService.checkExistedEmailorUsername({ username: payload.username }); if (countPerformerUsername || countUserUsername) { throw new UsernameExistedException(); } const countPerformerEmail = await this.performerModel.countDocuments({ email: payload.email.toLowerCase() }); const countUserEmail = await this.userService.checkExistedEmailorUsername({ email: payload.email }); if (countPerformerEmail || countUserEmail) { throw new EmailExistedException(); } if (payload.avatarId) { const avatar = await this.fileService.findById(payload.avatarId); if (!avatar) { throw new EntityNotFoundException('Avatar not found!'); } // TODO - check for other storaged data.avatarPath = avatar.path; } data.username = data.username.trim().toLowerCase(); data.email = data.email.toLowerCase(); if (!data.name) { data.name = data.firstName && data.lastName ? [data.firstName, data.lastName].join(' ') : 'No_display_name'; } if (data.dateOfBirth) { data.dateOfBirth = new Date(data.dateOfBirth); } const performer = await this.performerModel.create(data); await Promise.all([ payload.idVerificationId && this.fileService.addRef(payload.idVerificationId, { itemId: performer._id as any, itemType: REF_TYPE.PERFORMER }), payload.documentVerificationId && this.fileService.addRef(payload.documentVerificationId, { itemId: performer._id as any, itemType: REF_TYPE.PERFORMER }), payload.avatarId && this.fileService.addRef(payload.avatarId, { itemId: performer._id as any, itemType: REF_TYPE.PERFORMER }) ]); const adminEmail = await SettingService.getValueByKey(SETTING_KEYS.ADMIN_EMAIL); adminEmail && await this.mailService.send({ subject: 'New model sign up', to: adminEmail, data: performer, template: 'new-performer-notify-admin' }); // TODO - fire event? return new PerformerDto(performer); } public async adminUpdate( id: string | ObjectId, payload: PerformerUpdatePayload ): Promise<any> { const performer = await this.performerModel.findById(id); if (!performer) { throw new EntityNotFoundException(); } const data = { ...payload } as any; if (!data.name) { data.name = [data.firstName || '', data.lastName || ''].join(' '); } if (data.email && data.email.toLowerCase() !== performer.email) { const emailCheck = await this.performerModel.countDocuments({ email: data.email.toLowerCase(), _id: { $ne: performer._id } }); const countUserEmail = await this.userService.checkExistedEmailorUsername({ email: data.email }); if (emailCheck || countUserEmail) { throw new EmailExistedException(); } data.email = data.email.toLowerCase(); } if (data.username && data.username.trim() !== performer.username) { const usernameCheck = await this.performerModel.countDocuments({ username: data.username.trim().toLowerCase(), _id: { $ne: performer._id } }); const countUserUsername = await this.userService.checkExistedEmailorUsername({ username: data.username }); if (usernameCheck || countUserUsername) { throw new UsernameExistedException(); } data.username = data.username.trim().toLowerCase(); } if ( (payload.avatarId && !performer.avatarId) || (performer.avatarId && payload.avatarId && payload.avatarId !== performer.avatarId.toString()) ) { const avatar = await this.fileService.findById(payload.avatarId); if (!avatar) { throw new EntityNotFoundException('Avatar not found!'); } // TODO - check for other storaged data.avatarPath = avatar.path; } if ( (payload.coverId && !performer.coverId) || (performer.coverId && payload.coverId && payload.coverId !== performer.coverId.toString()) ) { const cover = await this.fileService.findById(payload.coverId); if (!cover) { throw new EntityNotFoundException('Cover not found!'); } // TODO - check for other storaged data.coverPath = cover.path; } if (data.dateOfBirth) { data.dateOfBirth = new Date(data.dateOfBirth); } await this.performerModel.updateOne({ _id: id }, data); const newPerformer = await this.performerModel.findById(performer._id); const oldStatus = performer.status; // fire event that updated performer status if (data.status !== performer.status) { await this.queueEventService.publish( new QueueEvent({ channel: PERFORMER_UPDATE_STATUS_CHANNEL, eventName: EVENT.UPDATED, data: { ...new PerformerDto(newPerformer), oldStatus } }) ); } // update auth key if email has changed if (data.email && data.email.toLowerCase() !== performer.email) { await this.authService.sendVerificationEmail({ email: newPerformer.email, _id: newPerformer._id }); await this.authService.updateKey({ source: 'performer', sourceId: newPerformer._id, type: 'email' }); } // update auth key if username has changed if ((data.username && data.username.trim() !== performer.username)) { await this.authService.updateKey({ source: 'performer', sourceId: newPerformer._id, type: 'username' }); } return true; } public async selfUpdate( id: string | ObjectId, payload: SelfUpdatePayload ): Promise<boolean> { const performer = await this.performerModel.findById(id); if (!performer) { throw new EntityNotFoundException(); } const data = { ...payload } as any; if (!data.name) { data.name = [data.firstName || '', data.lastName || ''].join(' '); } if (data.email && data.email.toLowerCase() !== performer.email) { const emailCheck = await this.performerModel.countDocuments({ email: data.email.toLowerCase(), _id: { $ne: performer._id } }); const countUserEmail = await this.userService.checkExistedEmailorUsername({ email: data.email }); if (emailCheck || countUserEmail) { throw new EmailHasBeenTakenException(); } data.email = data.email.toLowerCase(); } if (data.username && data.username.trim() !== performer.username) { const usernameCheck = await this.performerModel.countDocuments({ username: data.username.trim().toLowerCase(), _id: { $ne: performer._id } }); const countUserUsername = await this.userService.checkExistedEmailorUsername({ username: data.username }); if (usernameCheck || countUserUsername) { throw new UsernameExistedException(); } data.username = data.username.trim().toLowerCase(); } if (data.dateOfBirth) { data.dateOfBirth = new Date(data.dateOfBirth); } await this.performerModel.updateOne({ _id: id }, data); const newPerformer = await this.performerModel.findById(id); // update auth key if email has changed if (data.email && data.email.toLowerCase() !== performer.email) { await this.authService.sendVerificationEmail({ email: newPerformer.email, _id: newPerformer._id }); await this.authService.updateKey({ source: 'performer', sourceId: newPerformer._id, type: 'email' }); } // update auth key if username has changed if (data.username && data.username.trim() !== performer.username) { await this.authService.updateKey({ source: 'performer', sourceId: newPerformer._id, type: 'username' }); } return true; } public async updateAvatar(user: PerformerDto, file: FileDto) { await this.performerModel.updateOne( { _id: user._id }, { avatarId: file._id, avatarPath: file.path } ); await this.fileService.addRef(file._id, { itemId: user._id, itemType: REF_TYPE.PERFORMER }); // resend user info? // TODO - check others config for other storage return file; } public async updateCover(user: PerformerDto, file: FileDto) { await this.performerModel.updateOne( { _id: user._id }, { coverId: file._id, coverPath: file.path } ); await this.fileService.addRef(file._id, { itemId: user._id, itemType: REF_TYPE.PERFORMER }); return file; } public async updateWelcomeVideo(user: PerformerDto, file: FileDto) { await this.performerModel.updateOne( { _id: user._id }, { welcomeVideoId: file._id, welcomeVideoPath: file.path } ); await this.fileService.addRef(file._id, { itemId: user._id, itemType: REF_TYPE.PERFORMER }); await this.fileService.queueProcessVideo(file._id); if (user.welcomeVideoId) { await this.fileService.remove(user.welcomeVideoId); } return file; } public async checkSubscribed(performerId: string | ObjectId, user: UserDto) { const count = performerId && user ? await this.subscriptionService.checkSubscribed( performerId, user._id ) : 0; return { subscribed: count > 0 }; } public async updatePaymentGateway(payload: PaymentGatewaySettingPayload) { let item = await this.paymentGatewaySettingModel.findOne({ key: payload.key, performerId: payload.performerId }); if (!item) { // eslint-disable-next-line new-cap item = new this.paymentGatewaySettingModel(); } item.key = payload.key; item.performerId = payload.performerId as any; item.status = 'active'; item.value = payload.value; return item.save(); } public async getPaymentSetting( performerId: string | ObjectId, service = 'ccbill' ) { return this.paymentGatewaySettingModel.findOne({ key: service, performerId }); } public async updateCommissionSetting( performerId: string, payload: CommissionSettingPayload ) { let item = await this.commissionSettingModel.findOne({ performerId }); if (!item) { // eslint-disable-next-line new-cap item = new this.commissionSettingModel(); } item.performerId = performerId as any; item.monthlySubscriptionCommission = payload.monthlySubscriptionCommission; item.yearlySubscriptionCommission = payload.yearlySubscriptionCommission; item.videoSaleCommission = payload.videoSaleCommission; item.productSaleCommission = payload.productSaleCommission; return item.save(); } public async updateBankingSetting( performerId: string, payload: BankingSettingPayload, currentUser: UserDto ) { if ( (currentUser.roles && currentUser.roles.indexOf('admin') === -1 && currentUser._id.toString() !== performerId) || (!currentUser.roles && currentUser && currentUser._id.toString() !== performerId) ) { throw new NotAcceptableException('Permission denied'); } let item = await this.bankingSettingModel.findOne({ performerId }); if (!item) { // eslint-disable-next-line new-cap item = new this.bankingSettingModel(payload); } item.performerId = performerId as any; item.firstName = payload.firstName; item.lastName = payload.lastName; item.SSN = payload.SSN; item.bankName = payload.bankName; item.bankAccount = payload.bankAccount; item.bankRouting = payload.bankRouting; item.bankSwiftCode = payload.bankSwiftCode; item.address = payload.address; item.city = payload.city; item.state = payload.state; item.country = payload.country; return item.save(); } public async updateVerificationStatus( userId: string | ObjectId ): Promise<any> { const user = await this.performerModel.findById(userId); if (!user) return true; return this.performerModel.updateOne( { _id: userId }, { verifiedEmail: true } ); } public async getCommissions(performerId: string | ObjectId) { return this.commissionSettingModel.findOne({ performerId }); } public async checkAuthDocument(req: any, user: UserDto) { const { query } = req; if (!query.documentId) { throw new ForbiddenException(); } if (user.roles && user.roles.indexOf('admin') > -1) { return true; } // check type video const file = await this.fileService.findById(query.documentId); if (!file || !file.refItems || (file.refItems[0] && file.refItems[0].itemType !== REF_TYPE.PERFORMER)) return false; if (file.refItems && file.refItems[0].itemId && user._id.toString() === file.refItems[0].itemId.toString()) { return true; } throw new ForbiddenException(); } }

Performer Details Code Samples:

import { PureComponent } from 'react'; import { Layout, Tabs, Button, message, Modal, Tooltip } from 'antd'; import { connect } from 'react-redux'; import { getVideos, moreVideo, getVods, moreVod, resetVideoState } from '@redux/video/actions'; import { getGalleries, moreGalleries, resetGalleryState } from '@redux/gallery/actions'; import { listProducts, moreProduct, resetProductState } from '@redux/product/actions'; import { performerService, paymentService, utilsService } from 'src/services'; import Head from 'next/head'; import { CheckCircleOutlined, LikeOutlined, ArrowLeftOutlined, EyeOutlined, MessageOutlined, ShoppingOutlined, VideoCameraOutlined, PictureOutlined, UsergroupAddOutlined } from '@ant-design/icons'; import { ScrollListVideo, VideoPlayer } from '@components/video'; import { ScrollListProduct } from '@components/product/scroll-list-item'; import { ConfirmSubscriptionPerformerForm, PerformerInfo } from '@components/performer'; import { ScrollListGallery } from '@components/gallery'; import { shortenLargeNumber } from '@lib/index'; import { IPerformer, IUser, IUIConfig, ISettings, ICountry } from 'src/interfaces'; import Error from 'next/error'; import Router from 'next/router'; import '@components/performer/performer.less'; interface IProps { ui: IUIConfig; countries: ICountry[]; error: any; settings: ISettings; currentUser: IUser; performer: IPerformer; query: any; listProducts: Function; getVideos: Function; moreVideo: Function; getVods: Function; moreProduct: Function; moreVod: Function; videoState: any; saleVideoState: any; productState: any; getGalleries: Function; moreGalleries: Function; galleryState: any; resetVideoState: Function; resetProductState: Function; resetGalleryState: Function; } const { TabPane } = Tabs; class PerformerProfile extends PureComponent<IProps> { static authenticate = true; static noredirect = true; subscriptionType = 'monthly'; state = { tab: 'video', itemPerPage: 24, videoPage: 0, vodPage: 0, productPage: 0, galleryPage: 0, showWelcomVideo: false, openSubscriptionModal: false, submiting: false }; static async getInitialProps({ ctx }) { try { const { query } = ctx; const [performer, countries] = await Promise.all([ performerService.findOne(query.username, { Authorization: ctx.token || '' }), utilsService.countriesList() ]); return { performer: performer.data, countries: countries.data }; } catch (e) { return { error: await e }; } } async componentDidMount() { const { performer } = this.props; if (performer) { const notShownWelcomeVideos = localStorage.getItem('notShownWelcomeVideos'); const showWelcomVideo = !notShownWelcomeVideos || (notShownWelcomeVideos && !notShownWelcomeVideos.includes(performer._id)); this.setState({ showWelcomVideo }); this.loadItems(); } } componentWillUnmount() { const { resetGalleryState: resetGal, resetProductState: resetProd, resetVideoState: resetVid } = this.props; resetGal(); resetProd(); resetVid(); } loadMoreItem = async () => { const { moreVideo: moreVideoHandler, moreProduct: moreProductHandler, moreGalleries: moreGalleryHandler, moreVod: moreVodHandler, videoState: videosVal, productState: productsVal, saleVideoState: saleVideosVal, galleryState: galleryVal, performer } = this.props; const { videoPage, itemPerPage, vodPage, productPage, galleryPage, tab } = this.state; if (tab === 'video') { if (videosVal.items.length >= videosVal.total) return; this.setState({ videoPage: videoPage + 1 }, () => moreVideoHandler({ limit: itemPerPage, offset: (videoPage + 1) * itemPerPage, performerId: performer._id, isSaleVideo: false })); } if (tab === 'saleVideo') { if (saleVideosVal.items.length >= saleVideosVal.total) return; this.setState({ vodPage: vodPage + 1 }, () => moreVodHandler({ limit: itemPerPage, offset: (vodPage + 1) * itemPerPage, performerId: performer._id, isSaleVideo: true })); } if (tab === 'gallery') { if (galleryVal.items.length >= galleryVal.total) return; this.setState({ galleryPage: galleryPage + 1 }, () => moreGalleryHandler({ limit: itemPerPage, offset: (galleryPage + 1) * itemPerPage, performerId: performer._id })); } if (tab === 'store') { if (productsVal.items.length >= productsVal.total) return; this.setState({ productPage: productPage + 1 }, () => moreProductHandler({ limit: itemPerPage, offset: (productPage + 1) * itemPerPage, performerId: performer._id })); } }; loadItems = () => { const { itemPerPage, tab } = this.state; const { performer, getGalleries: getGalleriesHandler, listProducts: listProductsHandler, getVideos: getVideosHandler, getVods: getVodsHandler } = this.props; switch (tab) { case 'video': this.setState({ videoPage: 0 }, () => { getVideosHandler({ limit: itemPerPage, offset: 0, performerId: performer._id, isSaleVideo: false }); }); break; case 'saleVideo': this.setState({ vodPage: 0 }, () => { getVodsHandler({ limit: itemPerPage, offset: 0, performerId: performer._id, isSaleVideo: true }); }); break; case 'gallery': this.setState({ galleryPage: 0 }, () => { getGalleriesHandler({ limit: itemPerPage, offset: 0, performerId: performer._id }); }); break; case 'store': this.setState({ productPage: 0 }, () => { listProductsHandler({ performerId: performer._id, limit: itemPerPage, offset: 0 }); }); break; default: break; } } handleViewWelcomeVideo() { const { performer } = this.props; const notShownWelcomeVideos = localStorage.getItem('notShownWelcomeVideos'); if (!notShownWelcomeVideos?.includes(performer._id)) { const Ids = JSON.parse(notShownWelcomeVideos || '[]'); const values = Array.isArray(Ids) ? Ids.concat([performer._id]) : [performer._id]; localStorage.setItem('notShownWelcomeVideos', JSON.stringify(values)); } this.setState({ showWelcomVideo: false }); } handleClickMessage() { const { currentUser, performer } = this.props; if (!currentUser._id) { message.error('Please login!'); return; } if (!performer.isSubscribed) { message.error('Please subscribe to the model to start chatting.'); return; } Router.push({ pathname: '/messages', query: { toSource: 'performer', toId: performer?._id } }); } async subscribe(paymentGateway = 'ccbill') { const { performer } = this.props; try { await this.setState({ submiting: true }); const resp = await ( await paymentService.subscribe({ type: this.subscriptionType, performerId: performer._id, paymentGateway }) ).data; message.info('Redirecting to payment gateway, do not reload page at this time', 30); if (['ccbill', 'verotel'].includes(paymentGateway)) window.location.href = resp.paymentUrl; } catch (e) { const err = await e; message.error(err?.message || 'error occured, please try again later'); this.setState({ submiting: false }); } } render() { const { error, performer, ui, countries, settings, currentUser, videoState: videoProps, productState: productProps, saleVideoState: saleVideoProps, galleryState: galleryProps } = this.props; if (error) { return <Error statusCode={error?.statusCode} title={error?.message} />; } const { items: videos = [], total: totalVideos, requesting: loadingVid } = videoProps; const { items: saleVideos = [], total: totalVods, requesting: loadingVod } = saleVideoProps; const { items: products, total: totalProducts, requesting: loadingProduct } = productProps; const { items: galleries, total: totalGalleries, requesting: loadingGallery } = galleryProps; const { showWelcomVideo, openSubscriptionModal, submiting } = this.state; return ( <Layout> <Head> <title> {`${ui?.siteName} | ${performer?.name || performer?.username || ''}`} </title> <meta name="keywords" content={`${performer?.username}, ${performer?.name}`} /> <meta name="description" content={performer?.bio} /> {/* OG tags */} <meta property="og:title" content={`${ui?.siteName} | ${performer?.name || performer?.username || ''}`} key="title" /> <meta property="og:image" content={performer?.avatar || '/no-avatar.png'} /> <meta property="og:keywords" content={`${performer?.username}, ${performer?.name}`} /> <meta property="og:description" content={performer?.bio} /> {/* Twitter tags */} <meta name="twitter:title" content={`${ui?.siteName} | ${performer?.name || performer?.username || ''}`} /> <meta name="twitter:image" content={performer?.avatar || '/no-avatar.png'} /> <meta name="twitter:description" content={performer?.bio} /> </Head> <div className="top-profile" style={{ backgroundImage: performer?.cover ? `url('${performer?.cover}')` : "url('/banner-image.jpg')" }} > <div className="bg-2nd"> <div className="main-container"> <div className="top-banner"> <a aria-hidden className="arrow-back" onClick={() => Router.back()}> <ArrowLeftOutlined /> </a> <div className="stats-row"> <Tooltip title={performer?.name}> <div className="t-user-name"> {performer?.name || 'N/A'} &nbsp; {performer?.verifiedAccount && <CheckCircleOutlined />} </div> </Tooltip> <div className="tab-stat"> <div className="tab-item"> <span> {shortenLargeNumber(performer?.stats?.views || 0)} {' '} <EyeOutlined /> </span> </div> <div className="tab-item"> <span> {shortenLargeNumber(performer?.stats?.totalVideos || 0)} {' '} <VideoCameraOutlined /> </span> </div> <div className="tab-item"> <span> {shortenLargeNumber(performer?.stats?.totalPhotos || 0)} {' '} <PictureOutlined /> </span> </div> <div className="tab-item"> <span> {shortenLargeNumber(performer?.stats?.totalProducts || 0)} {' '} <ShoppingOutlined /> </span> </div> <div className="tab-item"> <span> {shortenLargeNumber(performer?.stats?.likes || 0)} {' '} <LikeOutlined /> </span> </div> <div className="tab-item"> <span> {shortenLargeNumber(performer?.stats?.subscribers || 0)} {' '} <UsergroupAddOutlined /> </span> </div> </div> </div> </div> </div> </div> </div> <div className="main-profile"> <div className="main-container"> <div className="fl-col"> <img alt="Avatar" src={performer?.avatar || '/no-avatar.png'} /> <div className="m-user-name"> <Tooltip title={performer?.name}> <h4> {performer?.name || 'N/A'} &nbsp; {performer?.verifiedAccount && <CheckCircleOutlined />} </h4> </Tooltip> <h5> @ {performer?.username || 'n/a'} </h5> </div> </div> <div className="btn-grp"> {currentUser && !currentUser.isPerformer && ( <Button className="primary" onClick={() => this.handleClickMessage()}> <MessageOutlined /> {' '} Message </Button> )} </div> <div className={currentUser.isPerformer ? 'mar-0 pro-desc' : 'pro-desc'}> <div className="show-more"> <PerformerInfo countries={countries} performer={performer} /> </div> </div> {!performer?.isSubscribed && ( <div className="subscription-bl"> <h5> Monthly Subscription </h5> <button type="button" className="sub-btn" disabled={(submiting && this.subscriptionType === 'monthly') || currentUser?.isPerformer} onClick={() => { this.subscriptionType = 'monthly'; this.setState({ openSubscriptionModal: true }); }} > SUBSCRIBE FOR $ {' '} {performer?.monthlyPrice.toFixed(2)} </button> </div> )} {!performer?.isSubscribed && ( <div className="subscription-bl"> <h5> Yearly Subscription </h5> <button type="button" className="sub-btn" disabled={(submiting && this.subscriptionType === 'yearly') || currentUser?.isPerformer} onClick={() => { this.subscriptionType = 'yearly'; this.setState({ openSubscriptionModal: true }); }} > SUBSCRIBE FOR $ {' '} {performer?.yearlyPrice.toFixed(2)} </button> </div> )} </div> </div> <div className="main-container"> <div className="model-content"> <Tabs defaultActiveKey="Video" className="model-tabs" size="large" onTabClick={(tab) => this.setState({ tab }, () => this.loadItems())}> <TabPane tab={<VideoCameraOutlined />} key="video" > <div className="heading-tab"> <h4> {totalVideos} {' '} VIDEO </h4> </div> <ScrollListVideo items={videos} loading={loadingVid} canLoadmore={videos && videos.length < totalVideos} loadMore={this.loadMoreItem.bind(this)} /> </TabPane> <TabPane tab={<img src="/video-crown.png" alt="$" />} key="saleVideo" > <div className="heading-tab"> <h4> {totalVods} {' '} VIDEO ON DEMAND </h4> </div> <ScrollListVideo items={saleVideos} loading={loadingVod} canLoadmore={saleVideos && saleVideos.length < totalVods} loadMore={this.loadMoreItem.bind(this)} /> </TabPane> <TabPane tab={<PictureOutlined />} key="gallery" > <div className="heading-tab"> <h4> {totalGalleries} {' '} GALLERY </h4> </div> <ScrollListGallery items={galleries} loading={loadingGallery} canLoadmore={galleries && galleries.length < totalGalleries} loadMore={this.loadMoreItem.bind(this)} /> </TabPane> <TabPane tab={<ShoppingOutlined />} key="store" > <div className="heading-tab"> <h4> {totalProducts} {' '} PRODUCT </h4> </div> <ScrollListProduct items={products} loading={loadingProduct} canLoadmore={products && products.length < totalProducts} loadMore={this.loadMoreItem.bind(this)} /> </TabPane> </Tabs> </div> </div> {performer && performer?.welcomeVideoPath && performer?.activateWelcomeVideo && ( <Modal key="welcome-video" destroyOnClose width={767} visible={showWelcomVideo} title={null} onCancel={() => this.setState({ showWelcomVideo: false })} footer={[ <Button key="close" className="secondary" onClick={() => this.setState({ showWelcomVideo: false })} > Close </Button>, <Button style={{ marginLeft: 0 }} key="close-show" className="primary" onClick={this.handleViewWelcomeVideo.bind(this)} > Don&apos;t show me again </Button> ]} > <VideoPlayer {...{ key: performer?._id, autoplay: true, controls: true, playsinline: true, fluid: true, sources: [ { src: performer?.welcomeVideoPath, type: 'video/mp4' } ] }} /> </Modal> )} <Modal key="subscribe_performer" centered width={600} title={null} visible={openSubscriptionModal} confirmLoading={submiting} footer={null} onCancel={() => this.setState({ openSubscriptionModal: false })} > <ConfirmSubscriptionPerformerForm settings={settings} type={this.subscriptionType || 'monthly'} performer={performer} submiting={submiting} onFinish={this.subscribe.bind(this)} /> </Modal> </Layout> ); } } const mapStates = (state: any) => ({ ui: { ...state.ui }, settings: { ...state.settings }, videoState: { ...state.video.videos }, saleVideoState: { ...state.video.saleVideos }, productState: { ...state.product.products }, galleryState: { ...state.gallery.galleries }, currentUser: { ...state.user.current } }); const mapDispatch = { getVideos, moreVideo, getVods, listProducts, moreProduct, getGalleries, moreGalleries, moreVod, resetProductState, resetVideoState, resetGalleryState }; export default connect(mapStates, mapDispatch)(PerformerProfile);