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'}
{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'}
{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'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);
Â