Skip to content
Snippets Groups Projects
Unverified Commit 53b16a52 authored by Khemissi Amir's avatar Khemissi Amir Committed by GitHub
Browse files

Adding the start session feature. (#3346)

parent cb545ce6
No related branches found
No related tags found
No related merge requests found
......@@ -35,9 +35,9 @@ RSpec/FilePath:
RSpec/MessageSpies:
Enabled: false
# Enable having lines with up to 130 charachters in length.
# Enable having lines with up to 150 charachters in length.
Layout/LineLength:
Max: 130
Max: 150
# Avoid methods longer than 10 lines of code.
Metrics/MethodLength:
......
$primary: #1a454a;
$primary: rgb(26, 69, 74);
$secondary: rgb(241, 240, 240);
@import 'bootstrap/scss/bootstrap';
@import 'bootstrap-icons/font/bootstrap-icons';
// TODO: Hadi- Move this to specific file to only be accessible where needed
#rooms-card {
.rooms-card {
&:hover {
background: rgba(26, 69, 74, 0.2)
}
}
#room-card-top {
.room-card-top {
&:hover {
cursor: pointer;
}
......
......@@ -3,14 +3,15 @@
module Api
module V1
class RoomsController < ApplicationController
before_action :find_room, only: :show
skip_before_action :verify_authenticity_token # TODO: amir - Revisit this.
before_action :find_room, only: %i[show start]
# GET /api/v1/rooms.json
# Returns: { data: Array[serializable objects(rooms)] , errors: Array[String] }
# Does: Returns the Rooms that belong to the user currently logged in
def index
# Return the rooms that belong to current user
rooms = Room.where(user_id: current_user.id)
rooms = Room.where(user_id: current_user&.id)
render json: {
data: rooms,
......@@ -32,12 +33,36 @@ module Api
end
end
# POST /api/v1/room/:friendly_id/start.json
# Returns: { data: Array[serializable objects] , errors: Array[String] }
# Does: Starts the Room session and joins in the room starter.
def start
# TODO: amir - Check the legitimately of the action.
bbb_api = BigBlueButtonApi.new
room_id = @room.friendly_id
session_starter = current_user ? "user(id):#{current_user.id}" : 'unauthenticated user'
options = { logoutURL: request.headers['Referer'] || root_url }
retries = 0
begin
logger.info "Starting session for room(friendly_id):#{room_id} by #{session_starter}."
join_url = bbb_api.start_session room: @room, session_starter: current_user, options: options
logger.info "Session successfully started for room(friendly_id):#{room_id} by #{session_starter}."
render_json data: { join_url: }, status: :created
rescue BigBlueButton::BigBlueButtonException => e
retries += 1
logger.info "Retrying session start for room(friendly_id):#{room_id} because of error(key): #{e.key} #{retries} times..."
retry unless retries >= 3
raise e
end
end
private
def find_room
@room = Room.find_by(friendly_id: params[:friendly_id])
@room = Room.find_by!(friendly_id: params[:friendly_id])
end
end
end
end
......@@ -3,12 +3,15 @@
module Api
module V1
class UsersController < ApplicationController
skip_before_action :verify_authenticity_token # TODO: amir - Revisit this.
# POST /api/v1/users.json
# Expects: { user: { :name, :email, :password, :password_confirmation } }
# Returns: { data: Array[serializable objects] , errors: Array[String] }
# Does: Creates and saves a new user record in the database with the provided parameters.
def create
# TODO: amir - handle ensure accessibility for unauthenticated requests only.
user = User.new({ provider: 'greenlight' }.merge(user_params)) # TMP fix for presence validation of :provider
if user.save
render json: {
......
# frozen_string_literal: true
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session # TODO: amir - Enable CSRF with a new made strategy.
before_action do
# Unless the request format is explicitly json Rails will mitigate the responsability to CSR to handle it.
render 'components/index' unless valid_api_request?
end
# For requests omitting required params.
# For requests that raised an unkown exception.
# Note: The order of each rescue is important (The highest has the lowest priority).
rescue_from StandardError do |exception|
log_exception exception
render_json errors: [Rails.configuration.custom_error_msgs[:server_error]], status: :internal_server_error
end
rescue_from ActionController::ParameterMissing do |exception|
log_exception exception
render json: {
......@@ -17,11 +21,19 @@ class ApplicationController < ActionController::Base
}, status: :bad_request
end
rescue_from ActiveRecord::RecordNotFound do |exception|
log_exception exception
render json: {
data: [],
errors: [Rails.configuration.custom_error_msgs[:record_not_found]]
}, status: :not_found
end
# TODO: amir - Better Error handling.
def log_exception(exception)
logger.error exception.message
logger.error exception.backtrace.join("\n")
logger.error exception.backtrace.join("\n") # TODO: amir - Revisit this.
end
# Returns the current signed in User (if any)
......@@ -29,10 +41,17 @@ class ApplicationController < ActionController::Base
@current_user ||= User.find_by(id: session[:user_id])
end
def render_json(data: {}, errors: [], status: :ok)
render json: {
data:,
errors:
}, status:
end
private
# Ensures that requests to the API are explicit enough.
def valid_api_request?
request.format == :json && request.headers['Accept'].include?('application/json')
request.format == :json && request.headers['Accept']&.include?('application/json')
end
end
import React, { useCallback } from "react";
import { useNavigate } from "react-router";
import { Card, Container } from "react-bootstrap";
import ButtonLink from "./stylings/buttons/ButtonLink";
import { Button, Card, Container } from "react-bootstrap";
import { PersonSquare, Link45deg } from 'react-bootstrap-icons';
import usePostStartSession from "../hooks/mutations/rooms/StartSession";
import { Spinner } from "./stylings/Spinner";
export default function RoomCard(props) {
const {id, name} = props
const navigate = useNavigate()
const handleClick = useCallback( () => { navigate(id)}, [id] )
const navigateToRoomShow = useCallback( () => { navigate(id) }, [id] )
const { handleStartSession, isLoading } = usePostStartSession(id)
return (
<Container>
<Card id='rooms-card' style={{ width: '14rem' }} border='dark'>
<Card.Body id='room-card-top' onClick={handleClick}>
<Card className='rooms-card' style={{ width: '14rem' }} border='dark'>
<Card.Body className='room-card-top' onClick={navigateToRoomShow}>
<PersonSquare size={30}/>
<Card.Title> {name} </Card.Title>
{/* TODO: Hadi- Make last session dynamic per room */}
......@@ -21,7 +23,10 @@ export default function RoomCard(props) {
<Card.Body>
<hr />
<Link45deg id='clipboard-icon' size={20}/>
<ButtonLink className='float-end' to='#'> Start</ButtonLink>
<Button className='float-end' onClick={handleStartSession} disabled={isLoading} >
Start {' '}
{ isLoading && <Spinner/> }
</Button>
</Card.Body>
</Card>
</Container>
......
import React from "react";
import axios from "axios"
import {Col, Row} from "react-bootstrap";
import ButtonLink from "../stylings/buttons/ButtonLink";
import {Button, Col, Row} from "react-bootstrap";
import FeatureTabs from "./FeatureTabs";
import {Link, useParams} from "react-router-dom";
import {useQuery} from "react-query";
import {Spinner} from "../stylings/Spinner"
import {House} from "react-bootstrap-icons";
import GetRoomQuery from "../../hooks/queries/rooms/GetRoomQuery";
import usePostStartSession from "../../hooks/mutations/rooms/StartSession";
export default function RoomView() {
const { friendly_id } = useParams()
const { isLoading, error, data: room, isFetching } = GetRoomQuery(friendly_id)
const { isLoading: queryIsLoading, data: room } = GetRoomQuery(friendly_id)
const { handleStartSession, isLoading: startSessionIsLoading } = usePostStartSession(friendly_id)
if (isLoading) return <Spinner />
if (queryIsLoading) return <Spinner />
return (
<>
......@@ -30,7 +29,10 @@ export default function RoomView() {
{ room.name }
</Col>
<Col>
<ButtonLink to="/" variant="primary" className="float-end">Start Session</ButtonLink>
<Button variant="primary" className="float-end" onClick={handleStartSession} disabled={startSessionIsLoading} >
Start Session {' '}
{ startSessionIsLoading && <Spinner/> }
</Button>
</Col>
</Row>
<FeatureTabs />
......
import axios from "axios";
export const ENDPOINTS = {
signup: '/users.json'
signup: '/users.json',
start_session: (room_id) => `room/${room_id}/start.json`
}
const axiosInstance = axios.create(
......
import { useMutation } from "react-query";
import axios, { ENDPOINTS } from "../../../helpers/Axios";
export default function usePostStartSession(room_id) {
const startSession = () => axios.post( ENDPOINTS.start_session(room_id) )
const mutation = useMutation( startSession,
{ // Mutation config.
mutationKey: ENDPOINTS.start_session(room_id) ,
onError: (error) => { console.error('Error:',error.message) },
onSuccess: (response, data) => { console.info('Success, sent:',data,', got:',response) },
}
)
const handleStartSession = async () => {
try
{
const response = await mutation.mutateAsync()
const join_url = response.data.data.join_url // TODO: amir - Simplify this.
window.location.href = join_url
}catch(e){/* TODO: amir - Revisit this. */}
}
return { handleStartSession, ...mutation }
}
......@@ -7,17 +7,62 @@ class BigBlueButtonApi
# Sets a BigBlueButtonApi object for interacting with the API.
def bbb_server
# TODO: Amir - Protect the BBB secret.
# TODO: Hadi - Add additional logic here...
@bbb_server ||= BigBlueButton::BigBlueButtonApi.new(bbb_endpoint, bbb_secret, '1.8')
end
# Start a session for a specific room and returns the join URL.
def start_session(room:, session_starter:, options: {})
# TODO: amir - Revisit this.
create_options = default_create_opts.merge(options)
join_options = default_join_opts.merge(create_options) # TODO: amir - Revisit this (createTime,...).
meeting_id = room.friendly_id
user_name = session_starter&.name || 'Someone'
password = (session_starter && create_options[:moderatorPW]) || create_options[:attendeePW]
create_session meeting_name: room.name, meeting_id:, options: create_options
get_session_join_url meeting_id:, user_name:, password:, options: join_options
end
private
def create_session(meeting_name:, meeting_id:, options: {}, modules: nil)
bbb_server.create_meeting meeting_name, meeting_id, options, modules
end
def get_session_join_url(meeting_id:, user_name:, password:, options: {})
bbb_server.join_meeting_url meeting_id, user_name, password, options
end
def bbb_endpoint
ENV['BIGBLUEBUTTON_ENDPOINT']
ENV['BIGBLUEBUTTON_ENDPOINT'] || 'https://test-install.blindsidenetworks.com/bigbluebutton/api/'
end
def bbb_secret
ENV['BIGBLUEBUTTON_SECRET']
ENV['BIGBLUEBUTTON_SECRET'] || '8cd8ef52e8e101574e400365b55e11a6'
end
def default_create_opts
{
# TODO: amir - revisit this.
record: true,
logoutURL: 'http://localhost',
moderatorPW: 'mp',
attendeePW: 'ap',
moderatorOnlyMessage: 'Welcome Moderator',
muteOnStart: false,
guestPolicy: 'ALWAYS_ACCEPT',
'meta_gl-v3-listed': 'public',
'meta_bbb-origin-version': 3,
'meta_bbb-origin': 'Greenlight'
}
end
def default_join_opts
{
join_via_html5: true
}
end
end
......@@ -23,7 +23,10 @@ module Greenlight
# Custom error messages for the Client side.
config.custom_error_msgs = {
missing_params: 'Invalid or Missing parameters.' # TODO: amir - Add I18n.
# TODO: amir - Add I18n.
missing_params: 'Invalid or Missing parameters.',
record_not_found: 'Record Not Found',
server_error: 'Something Went Wrong'
}
end
end
# frozen_string_literal: true
Rails.application.routes.draw do
root 'components#index'
root 'components#index', via: :all
# All the Api endpoints must be under /api/v1 and must have an extension .json.
namespace :api do
namespace :v1 do
resources :sessions, only: %i[index create destroy]
resources :users, only: [:create]
resources :rooms, only: [:show, :index], param: :friendly_id
resources :rooms, only: %i[show index], param: :friendly_id
post '/room/:friendly_id/start', to: 'rooms#start', as: :start_session
end
end
match '*path', to: 'components#index', via: :all # Enable CSR for full fledged http requests.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment