Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Version History

Version 1 Current »

Performer Controller Code Samples

import {
  Controller,
  Injectable,
  UseGuards,
  Body,
  Post,
  HttpCode,
  HttpStatus,
  UsePipes,
  ValidationPipe,
  Put,
  Get,
  Param,
  Query,
  Request,
  UseInterceptors,
  Res,
  HttpException
} from '@nestjs/common';
import { Response, Request as Req } from 'express';
import {
  DataResponse,
  PageableData,
  getConfig,
  EntityNotFoundException,
  QueueEventService,
  QueueEvent
} from 'src/kernel';
import { AuthService } from 'src/modules/auth/services';
import { Roles, CurrentUser } from 'src/modules/auth/decorators';
import { UserInterceptor } from 'src/modules/auth/interceptors';
import { RoleGuard, AuthGuard } from 'src/modules/auth/guards';
import { FileUploadInterceptor, FileUploaded, FileDto } from 'src/modules/file';
import { UserDto } from 'src/modules/user/dtos';
import { FavouriteService } from 'src/modules/favourite/services';
import { SettingService } from 'src/modules/settings';
import { SETTING_KEYS } from 'src/modules/settings/constants';
import { CountryService } from 'src/modules/utils/services';
import {
  DELETE_FILE_TYPE,
  FileService,
  FILE_EVENT,
  MEDIA_FILE_CHANNEL
} from 'src/modules/file/services';
import { omit } from 'lodash';
import { EXCLUDE_FIELDS } from 'src/kernel/constants';
import { AccountNotFoundxception } from 'src/modules/user/exceptions';
import { PasswordIncorrectException } from 'src/modules/auth/exceptions';
import { ApiOperation, ApiSecurity, ApiTags } from '@nestjs/swagger';
import { PerformerBroadcastSetting } from '../payloads/performer-broadcast-setting.payload';
import { PERFORMER_STATUSES } from '../constants';
import { PerformerDto, IPerformerResponse, BlockSettingDto } from '../dtos';
import {
  PerformerUpdatePayload,
  PerformerSearchPayload,
  PerformerStreamingStatusUpdatePayload,
  BlockSettingPayload,
  DefaultPricePayload
} from '../payloads';
import { PerformerService, PerformerSearchService } from '../services';

@Injectable()
@Controller('performers')
@ApiTags('Performer')
export class PerformerController {
  constructor(
    private readonly authService: AuthService,
    private readonly performerService: PerformerService,
    private readonly performerSearchService: PerformerSearchService,
    private readonly favoriteService: FavouriteService,
    private readonly settingService: SettingService,
    private readonly countryService: CountryService,
    private readonly fileService: FileService,
    private readonly queueEventService: QueueEventService
  ) {}

  @Get('/me')
  @HttpCode(HttpStatus.OK)
  // @Roles('performer')
  // @UseGuards(RoleGuard)
  async me(@Request() request: Req): Promise<DataResponse<IPerformerResponse>> {
    const jwtToken = request.headers.authorization;
    const performer = await this.authService.getSourceFromJWT(jwtToken);
    if (!performer || performer.status !== PERFORMER_STATUSES.ACTIVE) {
      throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED);
    }

    const result = await this.performerService.getDetails(
      performer._id,
      jwtToken
    );
    return DataResponse.ok(new PerformerDto(result).toResponse(true));
  }

  @Get('/search')
  @HttpCode(HttpStatus.OK)
  @UseInterceptors(UserInterceptor)
  @UsePipes(new ValidationPipe({ transform: true }))
  async usearch(
    @Query() req: PerformerSearchPayload,
    @CurrentUser() user: UserDto,
    @Request() request: Req
  ): Promise<DataResponse<PageableData<IPerformerResponse>>> {
    const query = { ...req };
    // only query activated performer, sort by online time
    query.status = PERFORMER_STATUSES.ACTIVE;
    let ipClient =
      request.headers['x-forwarded-for'] || request.connection.remoteAddress;
    ipClient = Array.isArray(ipClient) ? ipClient.toString() : ipClient;
    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 userCountry = null;
    let countryCode = null;
    if (whiteListIps.indexOf(ipClient) === -1) {
      userCountry = await this.countryService.findCountryByIP(ipClient);
      if (
        userCountry &&
        userCountry.status === 'success' &&
        userCountry.countryCode
      ) {
        countryCode = userCountry.countryCode;
      }
    }
    const data = await this.performerSearchService.advancedSearch(
      query,
      user,
      countryCode
    );
    return DataResponse.ok({
      total: data.total,
      data: data.data
    });
  }

  @Put('/')
  @Roles('performer')
  @UseGuards(RoleGuard)
  @ApiSecurity('authorization')
  @ApiOperation({ summary: 'Update Performer'})
  async updatePerformer(
    @CurrentUser() currentPerformer: PerformerDto,
    @Body() payload: PerformerUpdatePayload,
    @Request() request: Req
  ): Promise<DataResponse<IPerformerResponse>> {
    await this.performerService.update(
      currentPerformer._id,
      omit(payload, EXCLUDE_FIELDS)
    );

    const performer = await this.performerService.getDetails(
      currentPerformer._id,
      request.headers.authorization
    );
    return DataResponse.ok(new PerformerDto(performer).toResponse(true));
  }

  @Get('/:username/view')
  @UseInterceptors(UserInterceptor)
  @HttpCode(HttpStatus.OK)
  async getDetails(
    @Param('username') performerUsername: string,
    @Request() req: Req,
    @CurrentUser() user: UserDto
  ): Promise<DataResponse<Partial<PerformerDto>>> {
    let ipClient =
      req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    ipClient = Array.isArray(ipClient) ? ipClient.toString() : ipClient;
    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 userCountry = null;
    let countryCode = null;
    if (whiteListIps.indexOf(ipClient) === -1) {
      userCountry = await this.countryService.findCountryByIP(ipClient);
      if (
        userCountry &&
        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 EntityNotFoundException();
    }

    if (user) {
      const favorite = await this.favoriteService.findOne({
        favoriteId: performer._id,
        ownerId: user._id
      });
      if (favorite) performer.isFavorite = true;
    }

    const [defaultGroupChatPrice, defaultC2CPrice] = await Promise.all([
      this.settingService.getKeyValue(SETTING_KEYS.GROUP_CHAT_DEFAULT_PRICE) || 0,
      this.settingService.getKeyValue(SETTING_KEYS.PRIVATE_C2C_PRICE) || 0
    ]);
    performer.privateCallPrice = typeof performer.privateCallPrice !== 'undefined' ? performer.privateCallPrice : defaultC2CPrice;
    performer.groupCallPrice = typeof performer.groupCallPrice !== 'undefined' ? performer.groupCallPrice : defaultGroupChatPrice;

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

  @Post('/documents/upload')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UseInterceptors(
    FileUploadInterceptor('performer-document', 'file', {
      destination: getConfig('file').documentDir
    })
  )
  async uploadPerformerDocument(
    @FileUploaded() file: FileDto,
    @CurrentUser() performer: PerformerDto,
    @Request() request: Req
  ): Promise<any> {
    return DataResponse.ok({
      ...file,
      url: `${file.getUrl()}?documentId=${file._id}&token=${
        request.headers.authorization
      }`
    });
  }

  @Post('/release-form/upload')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UseInterceptors(
    FileUploadInterceptor('performer-release-form', 'file', {
      destination: getConfig('file').documentDir
    })
  )
  async uploadPerformerReleaseForm(
    @FileUploaded() file: FileDto,
    @CurrentUser() performer: PerformerDto,
    @Request() request: Req
  ): Promise<any> {
    return DataResponse.ok({
      ...file,
      url: `${file.getUrl()}?documentId=${file._id}&token=${
        request.headers.authorization
      }`
    });
  }

  @Post('/avatar/upload')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UseInterceptors(
    FileUploadInterceptor('avatar', 'avatar', {
      destination: getConfig('file').avatarDir,
      generateThumbnail: true,
      replaceWithThumbail: true,
      thumbnailSize: getConfig('image').avatar
    })
  )
  async uploadPerformerAvatar(
    @FileUploaded() file: FileDto,
    @CurrentUser() performer: PerformerDto
  ): Promise<any> {
    await this.performerService.updateAvatar(performer._id, file);
    await this.fileService.addRef(file._id, {
      itemId: performer._id,
      itemType: 'performer-avatar'
    });
    await this.queueEventService.publish(
      new QueueEvent({
        channel: MEDIA_FILE_CHANNEL,
        eventName: FILE_EVENT.FILE_RELATED_MODULE_UPDATED,
        data: {
          type: DELETE_FILE_TYPE.FILEID,
          currentFile: performer.avatarId,
          newFile: file._id
        }
      })
    );
    return DataResponse.ok({
      ...file,
      url: file.getUrl()
    });
  }

  @Post('/streaming-status/update')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UsePipes(new ValidationPipe({ transform: true }))
  async updateStreamingStatus(
    @CurrentUser() currentPerformer: PerformerDto,
    @Body() payload: PerformerStreamingStatusUpdatePayload
  ): Promise<DataResponse<IPerformerResponse>> {
    await this.performerService.updateSteamingStatus(
      currentPerformer._id,
      payload.status || ''
    );

    const performer = await this.performerService.findById(
      currentPerformer._id
    );
    return DataResponse.ok(new PerformerDto(performer).toResponse(true));
  }

  @Post('/default-price/update')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UsePipes(new ValidationPipe({ transform: true }))
  async updateDefaultPrice(
    @CurrentUser() currentPerformer: PerformerDto,
    @Body() payload: DefaultPricePayload
  ): Promise<DataResponse<IPerformerResponse>> {
    await this.performerService.updateDefaultPrice(
      currentPerformer._id,
      payload
    );

    const performer = await this.performerService.findById(
      currentPerformer._id
    );
    return DataResponse.ok(new PerformerDto(performer).toResponse(true));
  }

  @Post('/broadcast-setting/update')
  @HttpCode(HttpStatus.OK)
  @Roles('performer')
  @UseGuards(RoleGuard)
  @UsePipes(new ValidationPipe({ transform: true }))
  async updateBroadcastSetting(
    @CurrentUser() currentPerformer: PerformerDto,
    @Body() payload: PerformerBroadcastSetting
  ): Promise<DataResponse<IPerformerResponse>> {
    await this.performerService.updateBroadcastSetting(
      currentPerformer._id,
      payload
    );

    const performer = await this.performerService.findById(
      currentPerformer._id
    );
    return DataResponse.ok(new PerformerDto(performer).toResponse(true));
  }


  @Get('/documents/auth/check')
  @HttpCode(HttpStatus.OK)
  async checkAuth(
    @Request() request: Req,
    @Res() response: Response
  ): Promise<Response> {
    if (!request.query.token) {
      return response.status(HttpStatus.UNAUTHORIZED).send();
    }
    const user = await this.authService.getSourceFromJWT(
      request.query.token as string
    );
    if (!user) {
      return response.status(HttpStatus.UNAUTHORIZED).send();
    }
    const valid = await this.performerService.checkAuthDocument(request, user);
    return response
      .status(valid ? HttpStatus.OK : HttpStatus.UNAUTHORIZED)
      .send();
  }
}

Performer Details / Live Page Code Samples

/* eslint-disable camelcase */
import React, { PureComponent } from 'react';
import Head from 'next/head';
import {
  Row, Col, Button, message, ButtonProps
} from 'antd';
import { connect } from 'react-redux';
import { IPerformer, IUser } from 'src/interfaces';
import { messageService, streamService } from 'src/services';
import { LivePublisher } from '@components/streaming/publisher';
import { SocketContext, Event } from 'src/socket';
import {
  getStreamConversationSuccess,
  loadStreamMessages,
  resetStreamMessage,
  resetAllStreamMessage,
  resetStreamConversation
} from '@redux/stream-chat/actions';
import { updateStreamingStatus } from '@redux/performer/actions';
import { WEBRTC_ADAPTOR_INFORMATIONS } from 'src/antmedia/constants';
import ChatBox from '@components/stream-chat/chat-box';
import UpdateSatusForm from '@components/performer/streaming-status-update-form';
import Router from 'next/router';
import { getResponseError } from '@lib/utils';
import './index.less';

// eslint-disable-next-line no-shadow
enum EVENT_NAME {
  ROOM_INFORMATIOM_CHANGED = 'public-room-changed',
  USER_LEFT_ROOM = 'USER_LEFT_ROOM'
}

interface P {
  resetStreamMessage: Function;
  resetAllStreamMessage: Function;
  getStreamConversationSuccess: Function;
  loadStreamMessages: Function;
  updateStreamingStatus: Function;
  activeConversation: any;
  performer: IPerformer;
  updating: boolean;
  updateSuccess: boolean;
  updateError: any;
  resetStreamConversation: Function;
}

interface S {
  loading: boolean;
  fetching: boolean;
  initialized: boolean;
  publish_started: boolean;
  total: number;
  members: IUser[];
}

class PerformerLivePage extends PureComponent<P, S> {
  static authenticate = true;

  private publisherRef: any;

  private btnRef: React.RefObject<HTMLElement> = React.createRef();

  private socket: SocketIOClient.Socket;

  constructor(props: P) {
    super(props);
    this.state = {
      loading: false,
      fetching: false,
      initialized: false,
      publish_started: false,
      total: 0,
      members: []
    };
  }

  componentDidMount() {
    this.socket = this.context;
    this.joinPublicRoom();
    window.addEventListener('beforeunload', this.onbeforeunload);
    Router.events.on('routeChangeStart', this.onbeforeunload);
  }

  componentDidUpdate(prevProps: P) {
    const { updateSuccess, updateError } = this.props;
    if (prevProps.updateSuccess !== updateSuccess && updateSuccess) {
      message.success('Update Status Success.');
    }

    if (prevProps.updateError !== updateError && updateError) {
      message.error(getResponseError(updateError));
    }
  }

  componentWillUnmount() {
    window.removeEventListener('beforeunload', this.onbeforeunload);
    Router.events.off('routeChangeStart', this.onbeforeunload);
  }

  handler({ total, members, conversationId }) {
    const { activeConversation } = this.props;
    if (activeConversation?.data?._id === conversationId) {
      this.setState({ total, members });
    }
  }

  handleUpdateStatusForm(data) {
    const { updateStreamingStatus: dispatchUpdateStreamingStatus } = this.props;
    dispatchUpdateStreamingStatus(data);
  }

  onbeforeunload = () => {
    this.leavePublicRoom();
  };

  start() {
    this.publisherRef && this.publisherRef.start();
  }

  stop() {
    const { initialized, publish_started } = this.state;
    if (!initialized || !publish_started) {
      window.location.reload();
      return;
    }

    if (window.confirm('Are you sure want to stop this live streaming!')) {
      window.location.reload();
    }
  }

  async callback(info: WEBRTC_ADAPTOR_INFORMATIONS) {
    const { activeConversation } = this.props;
    if (activeConversation && activeConversation.data) {
      this.socket = this.context;
      if (info === WEBRTC_ADAPTOR_INFORMATIONS.INITIALIZED) {
        this.setState({ initialized: true });
        try {
          this.setState({ loading: true });
          const resp = await streamService.goLive();
          this.publisherRef && this.publisherRef.publish(resp.data.sessionId);
        } catch (e) {
          const error = await Promise.resolve(e);
          message.error(getResponseError(error));
          this.setState({ loading: false });
        }
      } else if (info === WEBRTC_ADAPTOR_INFORMATIONS.PUBLISH_STARTED) {
        this.setState({ publish_started: true, loading: false });
        this.socket.emit('public-stream/live', {
          conversationId: activeConversation.data._id
        });
      } else if (info === WEBRTC_ADAPTOR_INFORMATIONS.PUBLISH_FINISHED) {
        this.setState({ loading: false, publish_started: false });
      } else if (info === WEBRTC_ADAPTOR_INFORMATIONS.CLOSED) {
        this.setState({
          loading: false,
          initialized: false,
          publish_started: false
        });
      }
    }
  }

  async joinPublicRoom() {
    const {
      loadStreamMessages: dispatchLoadStreamMessages,
      getStreamConversationSuccess: dispatchGetStreamConversationSuccess
    } = this.props;
    try {
      this.setState({ fetching: true });
      const resp = await streamService.goLive();
      const { conversation } = resp.data;
      if (conversation && conversation._id) {
        // this.publisherRef && this.publisherRef.start();
        dispatchGetStreamConversationSuccess({ data: conversation });
        dispatchLoadStreamMessages({
          conversationId: conversation._id,
          limit: 25,
          offset: 0,
          type: conversation.type
        });
        this.socket = this.context;
        this.socket
          && this.socket.emit('public-stream/join', {
            conversationId: conversation._id
          });
      }
    } catch (e) {
      const error = await Promise.resolve(e);
      message.error(getResponseError(error));
    } finally {
      this.setState({ fetching: false });
    }
  }

  leavePublicRoom() {
    const {
      activeConversation,
      resetStreamMessage: dispatchResetStreamMessage,
      resetStreamConversation: dispatchResetStreamConversation
    } = this.props;
    if (activeConversation && activeConversation.data) {
      const conversation = { ...activeConversation.data };
      this.socket && this.socket.emit('public-stream/leave', {
        conversationId: conversation._id
      });
      dispatchResetStreamMessage();
      dispatchResetStreamConversation();
    }

    if (this.btnRef.current) {
      this.btnRef.current.remove();
    }
  }

  userLeftRoomHandle({ username, conversationId }) {
    const { activeConversation } = this.props;
    if (activeConversation?.data?._id === conversationId) {
      const { total, members } = this.state;
      this.setState({
        total: total - 1,
        members: members.filter((m) => m.username !== username)
      });
    }
  }

  async removeAllMessage() {
    const {
      activeConversation,
      performer,
      resetAllStreamMessage: dispatchResetAllMessage
    } = this.props;
    if (
      !activeConversation.data
      || performer._id !== activeConversation.data.performerId
    ) {
      return;
    }

    try {
      if (!window.confirm('Are you sure you want to remove chat history?')) {
        return;
      }
      await messageService.deleteAllMessageInConversation(
        activeConversation.data._id
      );
      dispatchResetAllMessage({ conversationId: activeConversation.data._id });
    } catch (e) {
      const error = await Promise.resolve(e);
      message.error(getResponseError(error));
    }
  }

  render() {
    const { performer, activeConversation, updating } = this.props;
    const {
      loading, initialized, publish_started, members, total, fetching
    } = this.state;
    const btnProps: ButtonProps & React.RefAttributes<HTMLElement> = {
      ref: this.btnRef,
      loading,
      disabled: fetching,
      block: true
    };
    if (initialized && publish_started) {
      btnProps.type = 'text';
      btnProps.style = { marginBottom: 10, background: 'black', color: 'white' };
      btnProps.children = 'Stop broadcasting';
      btnProps.onClick = this.stop.bind(this);
    } else {
      btnProps.type = 'primary';
      btnProps.style = { marginBottom: 10 };
      btnProps.children = 'Start broadcasting';
      btnProps.onClick = this.start.bind(this);
    }

    return (
      <>
        <Head>
          <title>Go Live</title>
        </Head>

        <Event
          event={EVENT_NAME.ROOM_INFORMATIOM_CHANGED}
          handler={this.handler.bind(this)}
        />
        <Event
          event={EVENT_NAME.USER_LEFT_ROOM}
          handler={this.userLeftRoomHandle.bind(this)}
        />

        <Row>
          <Col xs={24} sm={24} md={12}>
            <UpdateSatusForm
              status={performer.streamingTitle}
              updating={updating}
              submit={this.handleUpdateStatusForm.bind(this)}
            />
            <Button {...btnProps} />
            <LivePublisher
              ref={(ref) => {
                this.publisherRef = ref;
              }}
              onChange={this.callback.bind(this)}
              configs={{
                debug: true,
                bandwidth: 900,
                localVideoId: 'publisher'
              }}
            />
          </Col>
          <Col xs={24} sm={24} md={12}>
            <ChatBox
              {...this.props}
              members={members}
              totalParticipant={total}
              currentPerformer={performer}
            />
            {activeConversation?.data && (
              <div style={{ margin: '10px' }}>
                <Button
                  type="primary"
                  onClick={this.removeAllMessage.bind(this)}
                >
                  Clear message history
                </Button>
              </div>
            )}
          </Col>
        </Row>
      </>
    );
  }
}

PerformerLivePage.contextType = SocketContext;

const mapStateToProps = (state) => ({
  performer: state.performer.current,
  updating: state.performer.updating,
  updateSuccess: state.performer.updateSuccess,
  updateError: state.performer.updateError,
  activeConversation: state.streamMessage.activeConversation
});
const mapDispatchs = {
  updateStreamingStatus,
  getStreamConversationSuccess,
  loadStreamMessages,
  resetStreamMessage,
  resetAllStreamMessage,
  resetStreamConversation
};
export default connect(mapStateToProps, mapDispatchs)(PerformerLivePage);

  • No labels