diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
index 065556bf..185489c0 100644
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -1,55 +1,55 @@
user();
$room = \App\OpenVidu\Room::where('name', $id)->first();
// this isn't a room, bye bye
if (!$room) {
return response()->json(['status' => 'error'], 404);
}
// there's no existing session
if (!$room->hasSession()) {
// TODO: only the room owner should be able to create the session
$room->createSession();
}
$response = $room->getSessionToken('PUBLISHER');
if (!empty(request()->input('screenShare'))) {
$add_token = $room->getSessionToken('PUBLISHER');
$response['shareToken'] = $add_token['token'];
}
return response()->json($response, 200);
}
/**
* Webhook as triggered from OpenVidu server
*
* @param \Illuminate\Http\Request $request The API request.
*
* @return \Illuminate\Http\Response The response
*/
public function webhook(Request $request)
{
return response('Success', 200);
}
}
diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php
index cc1c11cb..c4afa817 100644
--- a/src/app/OpenVidu/Room.php
+++ b/src/app/OpenVidu/Room.php
@@ -1,109 +1,114 @@
belongsTo('\App\User', 'user_id', 'id');
- }
-
private function client()
{
if (!self::$client) {
self::$client = new \GuzzleHttp\Client(
[
'http_errors' => false, // No exceptions from Guzzle
'base_uri' => \config('openvidu.api_url'),
'verify' => \config('openvidu.api_verify_tls'),
'auth' => [
\config('openvidu.api_username'),
\config('openvidu.api_password')
]
]
);
}
return self::$client;
}
public function createSession()
{
$response = $this->client()->request(
'POST',
"sessions",
[
'json' => [
'mediaMode' => 'ROUTED',
'recordingMode' => 'MANUAL'
]
]
);
if ($response->getStatusCode() !== 200) {
$this->session_id = null;
$this->save();
}
$session = json_decode($response->getBody(), true);
$this->session_id = $session['id'];
$this->save();
return $session;
}
public function getSessionToken($role = 'PUBLISHER')
{
$response = $this->client()->request(
'POST',
'tokens',
[
'json' => [
'session' => $this->session_id,
'role' => $role
]
]
);
$json = json_decode($response->getBody(), true);
return $json;
}
public function hasSession()
{
if (!$this->session_id) {
return false;
}
$response = $this->client()->request('GET', "sessions/{$this->session_id}");
return $response->getStatusCode() == 200;
}
+ /**
+ * The room owner.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\belongsTo
+ */
+ public function owner()
+ {
+ return $this->belongsTo('\App\User', 'user_id', 'id');
+ }
+
/**
* Any (additional) properties of this room.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function settings()
{
return $this->hasMany('App\OpenVidu\RoomSetting', 'room_id');
}
}
diff --git a/src/resources/js/meet.js b/src/resources/js/meet.js
index 5cfc7aab..0e1e4021 100644
--- a/src/resources/js/meet.js
+++ b/src/resources/js/meet.js
@@ -1,121 +1,129 @@
/**
* Application code for the Meet UI
*/
import routes from './routes-meet.js'
window.routes = routes
require('./bootstrap')
import AppComponent from '../vue/Meet/App'
import MenuComponent from '../vue/Meet/Widgets/Menu'
import store from './store'
const loader = '
'
const app = new Vue({
el: '#app',
components: {
AppComponent,
MenuComponent,
},
store,
router: window.router,
data() {
return {
isLoading: true,
}
},
methods: {
+ // Clear (bootstrap) form validation state
+ clearFormValidation(form) {
+ $(form).find('.is-invalid').removeClass('is-invalid')
+ $(form).find('.invalid-feedback').remove()
+ },
errorPage(code, msg) {
// Until https://github.com/vuejs/vue-router/issues/977 is implemented
// we can't really use router to display error page as it has two side
// effects: it changes the URL and adds the error page to browser history.
// For now we'll be replacing current view with error page "manually".
const map = {
400: "Bad request",
401: "Unauthorized",
403: "Access denied",
404: "Not found",
405: "Method not allowed",
500: "Internal server error"
}
if (!msg) msg = map[code] || "Unknown Error"
const error_page = ``
- $('#app').children(':not(nav)').remove()
+ $('#error-page').remove()
$('#app').append(error_page)
},
errorHandler(error) {
if (!error.response) {
// TODO: probably network connection error
} else {
this.errorPage(error.response.status, error.response.statusText)
}
},
// Set user state to "logged in"
loginUser(token, dashboard) {
store.commit('logoutUser') // destroy old state data
store.commit('loginUser')
localStorage.setItem('token', token)
axios.defaults.headers.common.Authorization = 'Bearer ' + token
if (dashboard !== false) {
this.$router.push(store.state.afterLogin || { name: 'dashboard' })
}
store.state.afterLogin = null
},
// Set user state to "not logged in"
- logoutUser() {
+ logoutUser(dashboard) {
store.commit('logoutUser')
localStorage.setItem('token', '')
delete axios.defaults.headers.common.Authorization
- this.$router.push({ name: 'dashboard' })
+
+ if (dashboard !== false) {
+ this.$router.push({ name: 'dashboard' })
+ }
},
// Display "loading" overlay inside of the specified element
addLoader(elem) {
$(elem).css({position: 'relative'}).append($(loader).addClass('small'))
},
// Remove loader element added in addLoader()
removeLoader(elem) {
$(elem).find('.app-loader').remove()
},
startLoading() {
this.isLoading = true
// Lock the UI with the 'loading...' element
let loading = $('#app > .app-loader').show()
if (!loading.length) {
$('#app').append($(loader))
}
},
// Hide "loading" overlay
stopLoading() {
$('#app > .app-loader').addClass('fadeOut')
this.isLoading = false
}
}
})
// Register additional icons
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faDesktop,
faExpand,
faMicrophone,
faPowerOff,
faVideo
} from '@fortawesome/free-solid-svg-icons'
// Register only these icons we need
library.add(
faDesktop,
faExpand,
faMicrophone,
faPowerOff,
faVideo
)
diff --git a/src/resources/js/routes-meet.js b/src/resources/js/routes-meet.js
index 1fa12eb2..0d68fb3e 100644
--- a/src/resources/js/routes-meet.js
+++ b/src/resources/js/routes-meet.js
@@ -1,16 +1,23 @@
import DashboardComponent from '../vue/Meet/Dashboard'
+import LoginComponent from '../vue/Login'
import RoomComponent from '../vue/Meet/Room'
const routes = [
{
path: '/meet',
name: 'dashboard',
component: DashboardComponent
},
+ {
+ path: '/meet/login',
+ name: 'login',
+ component: LoginComponent
+ },
{
path: '/meet/:room',
+ name: 'room',
component: RoomComponent
}
]
export default routes
diff --git a/src/resources/sass/app.scss b/src/resources/sass/app.scss
index 4d3e80c0..7f484f1a 100644
--- a/src/resources/sass/app.scss
+++ b/src/resources/sass/app.scss
@@ -1,370 +1,371 @@
@import 'variables';
@import 'bootstrap';
@import 'meet';
@import 'menu';
@import 'toast';
@import 'forms';
html,
body,
body > .outer-container {
height: 100%;
}
#app {
display: flex;
flex-direction: column;
min-height: 100%;
+ overflow: hidden;
& > nav {
flex-shrink: 0;
z-index: 12;
}
& > div.container {
flex-grow: 1;
margin-top: 2rem;
margin-bottom: 2rem;
}
& > .filler {
flex-grow: 1;
}
& > div.container + .filler {
display: none;
}
}
#error-page {
position: absolute;
top: 0;
height: 100%;
width: 100%;
align-items: center;
display: flex;
justify-content: center;
color: #636b6f;
z-index: 10;
background: white;
.code {
text-align: right;
border-right: 2px solid;
font-size: 26px;
padding: 0 15px;
}
.message {
font-size: 18px;
padding: 0 15px;
}
}
.app-loader {
background-color: $body-bg;
height: 100%;
width: 100%;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 8;
.spinner-border {
width: 120px;
height: 120px;
border-width: 15px;
color: #b2aa99;
}
&.small .spinner-border {
width: 25px;
height: 25px;
border-width: 3px;
}
&.fadeOut {
visibility: hidden;
opacity: 0;
transition: visibility 400ms linear, opacity 400ms linear;
}
}
pre {
margin: 1rem 0;
padding: 1rem;
background-color: $menu-bg-color;
}
.card-title {
font-size: 1.2rem;
font-weight: bold;
}
tfoot.table-fake-body {
background-color: #f8f8f8;
color: grey;
text-align: center;
td {
vertical-align: middle;
height: 8em;
}
tbody:not(:empty) + & {
display: none;
}
}
table {
td.buttons,
td.email,
td.price,
td.datetime,
td.selection {
width: 1%;
white-space: nowrap;
}
th.price,
td.price {
width: 1%;
text-align: right;
white-space: nowrap;
}
&.form-list {
margin: 0;
td {
border: 0;
&:first-child {
padding-left: 0;
}
&:last-child {
padding-right: 0;
}
}
button {
line-height: 1;
}
}
.btn-action {
line-height: 1;
padding: 0;
}
}
.list-details {
min-height: 1em;
& > ul {
margin: 0;
padding-left: 1.2em;
}
}
.plan-selector {
.plan-ico {
font-size: 3.8rem;
color: #f1a539;
border: 3px solid #f1a539;
width: 6rem;
height: 6rem;
margin-bottom: 1rem;
border-radius: 50%;
}
}
.plan-description {
& > ul {
padding-left: 1.2em;
&:last-child {
margin-bottom: 0;
}
}
}
#status-box {
background-color: lighten($green, 35);
.progress {
background-color: #fff;
height: 10px;
}
.progress-label {
font-size: 0.9em;
}
.progress-bar {
background-color: $green;
}
&.process-failed {
background-color: lighten($orange, 30);
.progress-bar {
background-color: $red;
}
}
}
#dashboard-nav {
display: flex;
flex-wrap: wrap;
justify-content: center;
& > a {
padding: 1rem;
text-align: center;
white-space: nowrap;
margin: 0.25rem;
text-decoration: none;
width: 150px;
&.disabled {
pointer-events: none;
opacity: 0.6;
}
.badge {
position: absolute;
top: 0.5rem;
right: 0.5rem;
}
}
svg {
width: 6rem;
height: 6rem;
margin: auto;
}
}
.form-separator {
position: relative;
margin: 1em 0;
display: flex;
justify-content: center;
hr {
border-color: #999;
margin: 0;
position: absolute;
top: 0.75em;
width: 100%;
}
span {
background: #fff;
padding: 0 1em;
z-index: 1;
}
}
// Various improvements for mobile
@include media-breakpoint-down(sm) {
.card {
border: 0;
}
.card-body {
padding: 0.5rem 0;
}
.form-group {
margin-bottom: 0.5rem;
}
.tab-content {
margin-top: 0.5rem;
}
.col-form-label {
color: #666;
font-size: 95%;
}
.form-group.plaintext .col-form-label {
padding-bottom: 0;
}
form.read-only.short label {
width: 35%;
& + * {
width: 65%;
}
}
#app > div.container {
margin-bottom: 1rem;
margin-top: 1rem;
}
#header-menu-navbar {
padding: 0;
}
#dashboard-nav > a {
width: 135px;
}
.table-sm:not(.form-list) {
tbody td {
padding: 0.75rem 0.5rem;
svg {
vertical-align: -0.175em;
}
& > svg {
font-size: 125%;
margin-right: 0.25rem;
}
}
}
}
// Openvidu webcomponent improvements
mat-sidenav-container {
z-index: 10 !important;
}
#dialogChooseRoom {
top: 0;
left: 0;
z-index: 10;
background: #fff;
& > .mat-card {
max-height: unset;
margin: 2em auto;
}
}
// Openvidu webcomponent improvements
mat-sidenav-container {
z-index: 10 !important;
}
#dialogChooseRoom {
top: 0;
left: 0;
z-index: 10;
background: #fff;
& > .mat-card {
max-height: unset;
margin: 2em auto;
}
}
diff --git a/src/resources/vue/Login.vue b/src/resources/vue/Login.vue
index 1b633c4c..3f136f77 100644
--- a/src/resources/vue/Login.vue
+++ b/src/resources/vue/Login.vue
@@ -1,76 +1,75 @@
-
diff --git a/src/resources/vue/Meet/App.vue b/src/resources/vue/Meet/App.vue
index d2c87675..090b4cab 100644
--- a/src/resources/vue/Meet/App.vue
+++ b/src/resources/vue/Meet/App.vue
@@ -1,50 +1,51 @@
diff --git a/src/resources/vue/Meet/Dashboard.vue b/src/resources/vue/Meet/Dashboard.vue
index 19e704cb..62120f39 100644
--- a/src/resources/vue/Meet/Dashboard.vue
+++ b/src/resources/vue/Meet/Dashboard.vue
@@ -1,20 +1,20 @@
This is publicly available content, user will be asked to
- authenticate when accessing /<room>.
+ authenticate when accessing /meet/<room>.
Here we could put "Join or create a room" button with room name input.
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
index 52132d61..a12e23d9 100644
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -1,156 +1,166 @@
diff --git a/src/routes/api.php b/src/routes/api.php
index e948b044..e1c25970 100644
--- a/src/routes/api.php
+++ b/src/routes/api.php
@@ -1,136 +1,125 @@
'api',
'prefix' => 'auth'
],
function ($router) {
Route::post('login', 'API\AuthController@login');
Route::group(
['middleware' => 'auth:api'],
function ($router) {
Route::get('info', 'API\AuthController@info');
Route::post('logout', 'API\AuthController@logout');
Route::post('refresh', 'API\AuthController@refresh');
}
);
}
);
Route::group(
[
'domain' => \config('app.domain'),
'middleware' => 'api',
'prefix' => 'auth'
],
function ($router) {
Route::post('password-reset/init', 'API\PasswordResetController@init');
Route::post('password-reset/verify', 'API\PasswordResetController@verify');
Route::post('password-reset', 'API\PasswordResetController@reset');
Route::get('signup/plans', 'API\SignupController@plans');
Route::post('signup/init', 'API\SignupController@init');
Route::post('signup/verify', 'API\SignupController@verify');
Route::post('signup', 'API\SignupController@signup');
}
);
Route::group(
[
'domain' => \config('app.domain'),
'middleware' => 'auth:api',
'prefix' => 'v4'
],
function () {
Route::apiResource('domains', API\V4\DomainsController::class);
Route::get('domains/{id}/confirm', 'API\V4\DomainsController@confirm');
Route::get('domains/{id}/status', 'API\V4\DomainsController@status');
Route::apiResource('entitlements', API\V4\EntitlementsController::class);
Route::apiResource('packages', API\V4\PackagesController::class);
Route::apiResource('skus', API\V4\SkusController::class);
Route::apiResource('users', API\V4\UsersController::class);
Route::get('users/{id}/status', 'API\V4\UsersController@status');
Route::apiResource('wallets', API\V4\WalletsController::class);
Route::get('wallets/{id}/transactions', 'API\V4\WalletsController@transactions');
Route::get('wallets/{id}/receipts', 'API\V4\WalletsController@receipts');
Route::get('wallets/{id}/receipts/{receipt}', 'API\V4\WalletsController@receiptDownload');
Route::post('payments', 'API\V4\PaymentsController@store');
Route::get('payments/mandate', 'API\V4\PaymentsController@mandate');
Route::post('payments/mandate', 'API\V4\PaymentsController@mandateCreate');
Route::put('payments/mandate', 'API\V4\PaymentsController@mandateUpdate');
Route::delete('payments/mandate', 'API\V4\PaymentsController@mandateDelete');
}
);
Route::group(
[
'domain' => \config('app.domain'),
'middleware' => 'api',
'prefix' => 'v4'
],
function () {
Route::get('meet/openvidu/{id}', 'API\V4\OpenViduController@joinOrCreate');
}
);
-Route::group(
- [
- 'domain' => \config('app.domain'),
- 'middleware' => 'api',
- 'prefix' => 'v4'
- ],
- function () {
- Route::get('meet/openvidu/{id}', 'API\V4\OpenViduController@joinOrCreate');
- }
-);
-
Route::group(
[
'domain' => \config('app.domain'),
],
function () {
Route::post('webhooks/payment/{provider}', 'API\V4\PaymentsController@webhook');
Route::post('webhooks/meet/openvidu', 'API\V4\OpenViduController@webhook');
}
);
Route::group(
[
'domain' => 'admin.' . \config('app.domain'),
'middleware' => ['auth:api', 'admin'],
'prefix' => 'v4',
],
function () {
Route::apiResource('domains', API\V4\Admin\DomainsController::class);
Route::get('domains/{id}/confirm', 'API\V4\Admin\DomainsController@confirm');
Route::apiResource('entitlements', API\V4\Admin\EntitlementsController::class);
Route::apiResource('packages', API\V4\Admin\PackagesController::class);
Route::apiResource('skus', API\V4\Admin\SkusController::class);
Route::apiResource('users', API\V4\Admin\UsersController::class);
Route::post('users/{id}/suspend', 'API\V4\Admin\UsersController@suspend');
Route::post('users/{id}/unsuspend', 'API\V4\Admin\UsersController@unsuspend');
Route::apiResource('wallets', API\V4\Admin\WalletsController::class);
Route::post('wallets/{id}/one-off', 'API\V4\Admin\WalletsController@oneOff');
Route::get('wallets/{id}/transactions', 'API\V4\Admin\WalletsController@transactions');
Route::apiResource('discounts', API\V4\Admin\DiscountsController::class);
}
);