From b1dcaf9c77473a1dea3dbe7c081b7fa501d13dd1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andrej=20Rama=C5=A1euski?= <andrej@x2.cz>
Date: Fri, 2 Dec 2022 00:16:28 +0100
Subject: [PATCH] Refactoring, podpora hostu mimo skupiny

---
 lib/SeMeet/Controller/Auth.pm                |   2 +-
 lib/SeMeet/Controller/Meets.pm               |  79 +++++++------
 lib/SeMeet/Controller/Users.pm               |   1 +
 lib/SeMeet/Helpers/Core.pm                   |  11 +-
 lib/SeMeet/I18N/cs.pm                        |   6 +-
 lib/SeMeet/Schema/Result/Meet.pm             |  31 ++++-
 lib/SeMeet/Schema/Result/MeetUser.pm         |  32 +++++
 lib/SeMeet/Schema/Result/MeetUser_view.pm    |  20 ++++
 lib/SeMeet/Schema/Result/Moderator.pm        |  23 ----
 openapi.yaml                                 |  38 +++---
 sql/migrations.sql                           |  19 +++
 templates/includes/meet.html.ep              |   2 +-
 templates/includes/meet_groups.html.ep       |  14 +--
 templates/includes/meet_moderators.html.ep   |  18 +--
 templates/includes/meet_participants.html.ep |  29 +++++
 templates/meet.html.ep                       | 116 ++++++++++---------
 16 files changed, 287 insertions(+), 154 deletions(-)
 create mode 100644 lib/SeMeet/Schema/Result/MeetUser.pm
 create mode 100644 lib/SeMeet/Schema/Result/MeetUser_view.pm
 delete mode 100644 lib/SeMeet/Schema/Result/Moderator.pm
 create mode 100644 templates/includes/meet_participants.html.ep

diff --git a/lib/SeMeet/Controller/Auth.pm b/lib/SeMeet/Controller/Auth.pm
index 96b8605..503bbca 100644
--- a/lib/SeMeet/Controller/Auth.pm
+++ b/lib/SeMeet/Controller/Auth.pm
@@ -6,7 +6,7 @@ sub callback ($c) {
     my $token  = $c->oidc->get_access_token( $c->param("code") );
     my $claims = $c->oauth_claims( $token->access_token );
 
-    my $octopus_user = $c->iapi->get('octopus/user?username=' . $claims->{preferred_username});
+    my $octopus_user = $c->iapi->get('octopus/users/' . $claims->{sub});
 
     my %user = (
         uuid        => $claims->{sub},
diff --git a/lib/SeMeet/Controller/Meets.pm b/lib/SeMeet/Controller/Meets.pm
index 27935af..8325967 100644
--- a/lib/SeMeet/Controller/Meets.pm
+++ b/lib/SeMeet/Controller/Meets.pm
@@ -47,12 +47,22 @@ sub get($c) {
     my $args = $c->req->json;
 
     my $meet = $c->_get($c->stash->{id}, 0) // return;
-    my $is_mod = $meet->moderators({ octid => $c->stash->{user}->id})->count;
+
+    my $users = { 0 => [], 1 => [] };
+
+    USER:
+    foreach my $user ( $meet->users ) {
+        push @{ $users->{ $user->is_moderator} }, {
+             id   => $user->id,
+             name => $user->user_name,
+        }
+    }
 
     $c->render(openapi => $c->spec_filter({
         $meet->get_columns,
-        groups       => $meet->cached_groups,
-        moderators   => $meet->cached_moderators,
+        groups       => $meet->cached_groups, #TODO: direct
+        moderators   => $users->{1},
+        participants => $users->{0},
     }, 'Meet'));
 }
 
@@ -64,13 +74,13 @@ sub list($c) {
         deleted  => undef,
         -or => [
             owner_id => $c->stash->{user}->id,
-            "groups.group_id" => { '-in' => $c->stash->{groups} },
-            "moderators.octid" => $c->stash->{user}->octid,
+            "meet_groups.group_id" => { '-in' => $c->stash->{groups} },
+            "meet_users.user_id" => $c->stash->{user}->id,
         ]
     },
     {
         order_by => 'name',
-        join     => ['groups', 'moderators'],
+        join     => ['meet_groups', 'meet_users'],
         distinct => 1,
     }
     );
@@ -81,10 +91,9 @@ sub list($c) {
     while ( my $meet = $meets->next()) {
         push @meets, $c->spec_filter({
             $meet->get_columns,
-            groups => $meet->cached_groups,
-            moderators => $meet->cached_moderators,
-#            is_owned   => ( $meet->owner_id == $c->stash->{user}->id),
-#            is_moderated => 0,
+            groups       => $meet->cached_groups,
+            moderators   => $meet->cached_moderators,
+            participants => [],
         }, 'Meet')
     }
 
@@ -113,48 +122,50 @@ sub add_groups($c) {
     $c->render( status  => 204, text => '' );
 }
 
-sub add_moderators($c) {
+sub delete_group($c) {
     $c->openapi->valid_input or return;
-    my $args = $c->req->json;
-
-    my $meet = $c->_get($c->stash->{id}, 2) // return;
 
-    IDS:
-    foreach my $user ( @{ $args->{users} } ) {
+    my $meet = $c->_get($c->stash->{id}, 1) // return;
 
-        $meet->find_or_create_related('moderators',
-            {
-                octid => $user->{id},
-                name  => $user->{name},
-            }
-        );
-    }
-    $meet->update_moderators_cache();
+    $meet->delete_related('meet_groups',
+        { group_id => $c->stash->{group_id} }
+    );
+    $meet->update_groups_cache();
 
     $c->render( status  => 204, text => '' );
 }
 
-sub delete_group($c) {
+sub add_users($c) {
     $c->openapi->valid_input or return;
+    my $args = $c->req->json;
 
-    my $meet = $c->_get($c->stash->{id}, 1) // return;
+    my $meet = $c->_get($c->stash->{id}, 2) // return;
+    my $is_moderator = $args->{kind} eq 'moderator' ? 't':'f';
 
-    $meet->delete_related('meet_groups',
-        { group_id => $c->stash->{group_id} }
-    );
-    $meet->update_groups_cache();
+    UUID:
+    foreach my $uuid ( @{ $args->{users} } ) {
+        my $user = $c->user_by_uuid($uuid) // next UUID;
+        $meet->find_or_create_related('meet_users',
+            {
+                user_id       => $user->id,
+                is_moderator  => $is_moderator,
+            }
+        );
+    }
+    $meet->update_moderators_cache() if $is_moderator;
 
     $c->render( status  => 204, text => '' );
 }
 
-sub delete_moderator($c) {
+sub delete_user($c) {
     $c->openapi->valid_input or return;
 
     my $meet = $c->_get($c->stash->{id}, 2) // return;
 
-    $meet->delete_related('moderators',
-        { octid => $c->stash->{user_id} }
+    $meet->delete_related('meet_users',
+        { id => $c->stash->{relation_id} }
     );
+
     $meet->update_moderators_cache();
 
     $c->render( status  => 204, text => '' );
@@ -205,6 +216,8 @@ sub meet($c) { #NENI API!
     return $c->error(404, 'NOT_FOUND') if ! $meet;
 
     my $roles = $meet->user_roles($user, $c->current_user->{groups});
+    $c->trace($roles);
+
 
     return $c->error(404, 'NOT_FOUND') if ! $roles->{any};
 
diff --git a/lib/SeMeet/Controller/Users.pm b/lib/SeMeet/Controller/Users.pm
index e3ea8df..7a9f0ea 100644
--- a/lib/SeMeet/Controller/Users.pm
+++ b/lib/SeMeet/Controller/Users.pm
@@ -55,6 +55,7 @@ sub list($c) {
 
         push @users, $c->spec_filter({
             id          => $user->{id},
+            uuid        => $user->{uuid},
             name        => $user->{displayname},
             username    => $user->{username},
             region      => join ' - ', grep { defined } ($ks, $ms),
diff --git a/lib/SeMeet/Helpers/Core.pm b/lib/SeMeet/Helpers/Core.pm
index f135ce8..05d4277 100644
--- a/lib/SeMeet/Helpers/Core.pm
+++ b/lib/SeMeet/Helpers/Core.pm
@@ -104,22 +104,23 @@ sub register  {
         return $data;
     });
 
-    $self->helper( user_by_octid => sub ($c, $octid) {
+    $self->helper( user_by_uuid => sub ($c, $uuid) {
         my $user = $c->schema->resultset('User')->find({
-            octid => $octid
+            uuid => $uuid
         });
 
         if ( ! $user ) {
-            my $octopus_user = $c->iapi->get("octopus/users/$octid");
+            my $octopus_user = $c->iapi->get("octopus/users/$uuid");
 
             if ( $octopus_user ) {
                 $user = $c->schema->resultset('User')->find_or_create(
                     {
-                        octid       => $octid,
+                        uuid        => $uuid,
+                        octid       => $octopus_user->{id},
                         username    => $octopus_user->{username},
                         displayname => $octopus_user->{displayname},
                     },
-                    { key => 'username' }
+                    { key => 'uuid' }
                 );
             }
         }
diff --git a/lib/SeMeet/I18N/cs.pm b/lib/SeMeet/I18N/cs.pm
index 207ecc6..f6102fc 100644
--- a/lib/SeMeet/I18N/cs.pm
+++ b/lib/SeMeet/I18N/cs.pm
@@ -19,18 +19,22 @@ our %Lexicon = (
     ERROR_MEET_NAME_REQURED             => "Název nové místnosti je povinný",
     ERROR_MEET_NAME_DUPLICITY           => 'Duplicitní název mistnosti',
     LABEL_GROUPS_ADD                    => 'Přidání skupin',
-    LABEL_USERS_ADD                     => 'Přidání moderatorů',
     ENTER                               => 'Vstoupit',
     COPY_LINK                           => 'ZkopĂ­rovat odkaz',
 
     'About meet'                        => 'O mistnosti',
     'Add groups'                        => 'Přidat skupiny',
     'Add moderators'                    => 'Přidat moderatory',
+    'Add people'                        => 'Přidat osoby',
     'Create meet'                       => 'Vytvořit místnost',
     'Delete meet'                       => 'Smazat mĂ­stnost',
     'Save changes'                      => 'Uložit změny',
     'Configuration'                     => 'Konfigurace',
     'Authorized groups'                 => 'Opravněné skupiny',
+    'Authorized persons'                => 'Opravněné osoby',
+    'Adding groups'                     => 'Přidávání skupin',
+    'Adding people'                     => 'Přidávání osob',
+    'Adding moderators'                 => 'Přidávání moderatorů',
     'Moderators'                        => 'Moderatoři',
     'Invites'                           => 'Pozvánky',
 );
diff --git a/lib/SeMeet/Schema/Result/Meet.pm b/lib/SeMeet/Schema/Result/Meet.pm
index 991a547..aaa221c 100644
--- a/lib/SeMeet/Schema/Result/Meet.pm
+++ b/lib/SeMeet/Schema/Result/Meet.pm
@@ -51,7 +51,7 @@ __PACKAGE__->has_many(
 );
 
 __PACKAGE__->has_many(
-    moderators => 'SeMeet::Schema::Result::Moderator',
+    meet_users => 'SeMeet::Schema::Result::MeetUser',
     { 'foreign.meet_id' => 'self.id', },
 );
 
@@ -60,6 +60,11 @@ __PACKAGE__->has_many(
     { 'foreign.meet_id' => 'self.id', },
 );
 
+__PACKAGE__->has_many(
+    users => 'SeMeet::Schema::Result::MeetUser_view',
+    { 'foreign.meet_id' => 'self.id', },
+);
+
 __PACKAGE__->inflate_column('properties', {
     inflate => sub { from_json(shift); },
     deflate => sub { to_json(shift); },
@@ -68,8 +73,22 @@ __PACKAGE__->inflate_column('properties', {
 sub user_roles($self, $user, $groups) {
 
     my $roles = {
-        participant => $self->meet_groups({ group_id => { -in => $groups } })->count(),
-        moderator   => $self->moderators({ octid => $user->octid })->count(),
+
+        participant => (
+            $self->meet_groups({
+                group_id => { -in => $groups }
+            })->count()
+            ||
+            $self->meet_users({
+                user_id => $user->id,
+            })->count()
+        ),
+
+        moderator   => $self->meet_users({
+            user_id      => $user->id,
+            is_moderator => 't',
+        })->count(),
+
         owner       => ($self->owner_id == $user->id),
     };
 
@@ -93,10 +112,10 @@ sub update_groups_cache($self) {
 
 sub update_moderators_cache($self) {
     my @moderators = ();
-    foreach my $moderator ($self->moderators) {
+    foreach my $moderator ($self->users({ is_moderator => 't'})) {
         push @moderators, {
-            id   => $moderator->octid,
-            name => $moderator->name,
+            id   => $moderator->id,
+            name => $moderator->user_name,
         };
     }
     my $properties =  $self->properties // {};
diff --git a/lib/SeMeet/Schema/Result/MeetUser.pm b/lib/SeMeet/Schema/Result/MeetUser.pm
new file mode 100644
index 0000000..10daf68
--- /dev/null
+++ b/lib/SeMeet/Schema/Result/MeetUser.pm
@@ -0,0 +1,32 @@
+package SeMeet::Schema::Result::MeetUser;
+
+use strict;
+use warnings;
+
+use base 'DBIx::Class::Core';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('meets_users');
+
+__PACKAGE__->add_columns(
+    id => {
+        data_type         => 'integer',
+        is_auto_increment => 1,
+        is_nullable       => 0,
+        sequence          => 'uid_seq'
+    },
+    qw(
+        meet_id
+        user_id
+        is_moderator
+    ),
+);
+
+__PACKAGE__->set_primary_key('id');
+
+__PACKAGE__->add_unique_constraint(
+    secondary => [qw(meet_id user_id is_moderator)]
+);
+
+1;
diff --git a/lib/SeMeet/Schema/Result/MeetUser_view.pm b/lib/SeMeet/Schema/Result/MeetUser_view.pm
new file mode 100644
index 0000000..6d5c37b
--- /dev/null
+++ b/lib/SeMeet/Schema/Result/MeetUser_view.pm
@@ -0,0 +1,20 @@
+package SeMeet::Schema::Result::MeetUser_view;
+
+use strict;
+use warnings;
+
+use base 'SeMeet::Schema::Result::MeetUser';
+
+our $VERSION = 1;
+
+__PACKAGE__->table('meets_users_view');
+
+__PACKAGE__->add_columns(
+    qw(
+        user_name
+    ),
+);
+
+1;
+
+
diff --git a/lib/SeMeet/Schema/Result/Moderator.pm b/lib/SeMeet/Schema/Result/Moderator.pm
deleted file mode 100644
index 4491de9..0000000
--- a/lib/SeMeet/Schema/Result/Moderator.pm
+++ /dev/null
@@ -1,23 +0,0 @@
-package SeMeet::Schema::Result::Moderator;
-
-use strict;
-use warnings;
-
-use base 'DBIx::Class::Core';
-
-our $VERSION = 1;
-
-__PACKAGE__->table('moderators');
-
-__PACKAGE__->add_columns(
-    qw(
-        meet_id
-        octid
-        name
-    ),
-);
-
-__PACKAGE__->set_primary_key('meet_id', 'octid');
-
-1;
-
diff --git a/openapi.yaml b/openapi.yaml
index 5672914..21fdd2c 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -48,6 +48,10 @@ components:
           type: array
           items:
              $ref: '#/components/schemas/UserInList'
+        participants:
+          type: array
+          items:
+             $ref: '#/components/schemas/UserInList'
     GroupInList:
       type: object
       properties:
@@ -63,6 +67,9 @@ components:
         id:
           type: integer
           readOnly: true
+        uuid:
+          type: string
+          readOnly: true
         name:
           type: string
           readOnly: true
@@ -272,7 +279,7 @@ paths:
                 items:
                   $ref: '#/components/schemas/UserInList'
 
-  /meets/{id}/group:
+  /meets/{id}/groups:
     post:
       x-mojo-to: meets#add_groups
       security:
@@ -307,7 +314,7 @@ paths:
         201:
           description: Groups authorized
 
-  /meets/{id}/group/{group_id}:
+  /meets/{id}/groups/{group_id}:
     delete:
       x-mojo-to: meets#delete_group
       security:
@@ -335,15 +342,15 @@ paths:
         204:
           description: Groups unauthorized
 
-  /meets/{id}/moderator:
+  /meets/{id}/users:
     post:
-      x-mojo-to: meets#add_moderators
+      x-mojo-to: meets#add_users
       security:
         - Bearer: []
       tags:
         - meets
-      summary: "Pridat moderatory"
-      operationId: addModeratorsToMeet
+      summary: "Pridat osoby/moderatory"
+      operationId: addUsersToMeet
       parameters:
       - name: id
         in: path
@@ -362,23 +369,22 @@ paths:
                 users:
                   type: array
                   items:
-                    type: object
-#                    $ref: '#/components/schemas/UserInList'
+                    type: string
               required:
                 - users
       responses:
         201:
-          description: Moderators added
+          description: Users added
 
-  /meets/{id}/moderator/{user_id}:
+  /meets/{id}/users/{relation_id}:
     delete:
-      x-mojo-to: meets#delete_moderator
+      x-mojo-to: meets#delete_user
       security:
         - Bearer: []
       tags:
         - meets
-      summary: "Smazat moderatora"
-      operationId: deleteModeratorFromMeet
+      summary: "Smazat osobu"
+      operationId: deleteUserFromMeet
       parameters:
       - name: id
         in: path
@@ -387,13 +393,13 @@ paths:
         description: "Identifikator mistnosti"
         schema:
           type: integer
-      - name: user_id
+      - name: relation_id
         in: path
         required: true
         example: 100345
-        description: "Identifikator moderatora"
+        description: "Identifikator relace"
         schema:
           type: integer
       responses:
         204:
-          description: Moderator deleted
+          description: User deleted
diff --git a/sql/migrations.sql b/sql/migrations.sql
index 04f4887..8e1e977 100644
--- a/sql/migrations.sql
+++ b/sql/migrations.sql
@@ -62,3 +62,22 @@ create table "moderators" (
     foreign key ("meet_id") references "meets" ("id") on update cascade on delete cascade
 );
 
+-- 5 up
+create table "meets_users" (
+    "id" integer not null default nextval('uid_seq'),
+    "meet_id" integer not null,
+    "user_id" integer not null,
+    "is_moderator" bool not null default 'f',
+    primary key("id"),
+    unique("meet_id", "user_id", "is_moderator"),
+    foreign key ("meet_id") references "meets" ("id") on update cascade on delete cascade,
+    foreign key ("user_id") references "users" ("id") on update cascade on delete cascade
+);
+
+create view "meets_users_view" as
+select "meets_users".*,
+    "users"."displayname" as "user_name"
+from "meets_users"
+join "users" on ("users"."id" = "meets_users"."user_id")
+;
+
diff --git a/templates/includes/meet.html.ep b/templates/includes/meet.html.ep
index 72b4028..feadf7c 100644
--- a/templates/includes/meet.html.ep
+++ b/templates/includes/meet.html.ep
@@ -36,6 +36,6 @@
     },
     };
 
-  const jitsi = new JitsiMeetExternalAPI(MEET_DOMAIN, MEET_OPTIONS);
+//  const jitsi = new JitsiMeetExternalAPI(MEET_DOMAIN, MEET_OPTIONS);
 </script>
 
diff --git a/templates/includes/meet_groups.html.ep b/templates/includes/meet_groups.html.ep
index 29545c3..b53c518 100644
--- a/templates/includes/meet_groups.html.ep
+++ b/templates/includes/meet_groups.html.ep
@@ -10,16 +10,16 @@
 </div>
 
 % if ( $roles->{owner}  || $roles->{moderator} ) {
-<div class="" v-effect="searchGroups()">
-  <label class="form-field__label" for="name"><%=l 'LABEL_GROUPS_ADD' %></label>
-  <input type="text" class="w-full text-input" value="" v-model="search_groups" id="search_group" placeholder="<%=l 'INPUT_MEET_ADD_GROUPS_PLACEHOLDER' %>"/>
-    <div class="overflow-y-auto h-64 mb-4 border p-2 text-sm" v-if="groups.length > 0">
-    <div v-for="group in groups" class="checkbox form-field__control mb-1" >
-     <input name="groups" type="checkbox" v-bind:title="group.id" v-bind:value="group.id"  v-model="selected_groups" >
+<div v-effect="searchGroups()">
+  <label class="form-field__label" for="name"><%=l 'Adding groups' %></label>
+  <input type="text" class="w-full text-input" value="" v-model="filter['group']" id="search_group" placeholder="<%=l 'INPUT_MEET_ADD_GROUPS_PLACEHOLDER' %>"/>
+    <div class="overflow-y-auto h-64 mb-4 border p-2 text-sm" v-if="search['group'].length > 0">
+    <div v-for="group in search['group']" class="checkbox form-field__control mb-1" >
+     <input name="groups" type="checkbox" v-bind:title="group.id" v-bind:value="group.id" v-model="selected['group']" >
      <label>{{group.name}}</label>
     </div>
     </div>
-    <div class="form-field" v-if="selected_groups.length > 0">
+    <div class="form-field" v-if="selected['group'].length > 0">
        <button class="btn btn--green-400 btn--hoveractive text-lg" @click="addGroups()">
         <div class="btn__body"><%=l 'Add groups' %></div>
        </button>
diff --git a/templates/includes/meet_moderators.html.ep b/templates/includes/meet_moderators.html.ep
index 2b637bb..5ae1dc6 100644
--- a/templates/includes/meet_moderators.html.ep
+++ b/templates/includes/meet_moderators.html.ep
@@ -3,24 +3,24 @@
 {{ moderator.name }}
 % if ( $roles->{owner} ) {
     <span class="icon">
-      <i class="ico--cross ml-3 cursor-pointer" @click="removeModerator(moderator.id)"></i>
+      <i class="ico--cross ml-3 cursor-pointer" @click="removeUser(moderator.id)"></i>
     </span>
 % }
 </span>
 </div>
 
 % if ( $roles->{owner} ) {
-<div v-effect="searchUsers()">
-  <label class="form-field__label" for="name"><%=l 'LABEL_USERS_ADD' %></label>
-  <input type="text" class="w-full text-input" value="" v-model="search_users" id="search_user" placeholder="<%=l 'INPUT_MEET_ADD_USERS_PLACEHOLDER' %>"/>
-    <div class="overflow-y-auto h-64 mb-4 border p-2 text-sm" v-if="users.length > 0">
-    <div v-for="user in users" class="checkbox form-field__control mb-1" >
-     <input name="users" type="checkbox" v-bind:title="user.id" v-bind:value="user.id"  v-model="selected_users" >
+<div v-effect="searchUsers('moderator')">
+  <label class="form-field__label" for="name"><%=l 'Adding moderators' %></label>
+  <input type="text" class="w-full text-input" value="" v-model="filter['moderator']" id="search_moderator" placeholder="<%=l 'INPUT_MEET_ADD_USERS_PLACEHOLDER' %>"/>
+    <div class="overflow-y-auto h-64 mb-4 border p-2 text-sm" v-if="search['moderator'].length > 0">
+    <div v-for="user in search['moderator']" class="checkbox form-field__control mb-1" >
+     <input name="moderators" type="checkbox" :value="user.uuid"  v-model="selected['moderator']" >
      <label style="max-width: 100%">{{user.name}} ({{ user.username }})<span v-if="user.region.length > 0" class="text-grey-300 text-xs"> | {{ user.region }}</span></label>
     </div>
     </div>
-    <div class="form-field" v-if="selected_users.length > 0">
-       <button class="btn btn--red-600 btn--hoveractive text-lg" @click="addModerators()">
+    <div class="form-field" v-if="selected['moderator'].length > 0">
+       <button class="btn btn--red-600 btn--hoveractive text-lg" @click="addUsers('moderator')">
         <div class="btn__body"><%=l 'Add moderators' %></div>
        </button>
     </div>
diff --git a/templates/includes/meet_participants.html.ep b/templates/includes/meet_participants.html.ep
new file mode 100644
index 0000000..f2427aa
--- /dev/null
+++ b/templates/includes/meet_participants.html.ep
@@ -0,0 +1,29 @@
+<div class="mb-4">
+<span v-for="user in meet.participants" class="chip chip--blue-300 mr-1 mb-1 rounded">
+{{ user.name }}
+% if ( $roles->{owner}  || $roles->{moderator} ) {
+    <span class="icon">
+      <i class="ico--cross ml-3 cursor-pointer" @click="removeUser(user.id)"></i>
+    </span>
+% }
+</span>
+</div>
+
+% if ( $roles->{owner}  || $roles->{moderator} ) {
+<div v-effect="searchUsers('participant')">
+  <label class="form-field__label" for="name"><%=l 'Adding people' %></label>
+  <input type="text" class="w-full text-input" value="" v-model="filter['participant']" id="search_participant" placeholder="<%=l 'INPUT_MEET_ADD_USERS_PLACEHOLDER' %>"/>
+    <div class="overflow-y-auto h-64 mb-4 border p-2 text-sm" v-if="search['participant'].length > 0">
+    <div v-for="user in search['participant']" class="checkbox form-field__control mb-1" >
+     <input name="participants" type="checkbox" :value="user.uuid"  v-model="selected['participant']" >
+     <label style="max-width: 100%">{{user.name}} ({{ user.username }})<span v-if="user.region.length > 0" class="text-grey-300 text-xs"> | {{ user.region }}</span></label>
+    </div>
+    </div>
+    <div class="form-field" v-if="selected['participant'].length > 0">
+       <button class="btn btn--blue-300 btn--hoveractive text-lg" @click="addUsers('participant')">
+        <div class="btn__body"><%=l 'Add people' %></div>
+       </button>
+    </div>
+</div>
+% }
+
diff --git a/templates/meet.html.ep b/templates/meet.html.ep
index d9904a9..485b5d8 100644
--- a/templates/meet.html.ep
+++ b/templates/meet.html.ep
@@ -3,8 +3,9 @@
 
 %= include 'includes/meet'
 
-<div class="grid grid-cols-3 border border-b-0 divide-x text-center">
+<div class="grid grid-cols-4 border border-b-0 divide-x text-center">
 <div @click="active_tab='groups'" class="p-4 bg-grey-125" :class="tabClass('groups')"><%=l 'Authorized groups' %></div>
+<div @click="active_tab='participants'" class="p-4 bg-grey-125" :class="tabClass('participants')"><%=l 'Authorized persons' %></div>
 <div @click="active_tab='moderators'" class="p-4 bg-grey-125" :class="tabClass('moderators')"><%=l 'Moderators' %></div>
 %#<div @click="active_tab='invites'" class="p-4 bg-grey-125" :class="tabClass('invites')"><%=l 'Invites' %></div>
 <div @click="active_tab='form'" class="p-4 bg-grey-125" :class="tabClass('form')"><%=l $roles->{owner} ? 'Configuration':'About meet' %></div>
@@ -13,6 +14,9 @@
 <div class="border p-4" v-effect="getMeet()" v-cloak>
     <div v-if="active_tab == 'groups'">
 %= include 'includes/meet_groups'
+    </div>
+    <div v-if="active_tab == 'participants'">
+%= include 'includes/meet_participants'
     </div>
     <div v-if="active_tab == 'moderators'">
 %= include 'includes/meet_moderators'
@@ -33,10 +37,9 @@
 
   const GROUPS_URL      = '/api/groups';
   const USERS_URL       = '/api/users';
-//const USERS_URL       = '<%= config->{iapi} %>octopus/users';
   const MEET_URL        = '/api/meets/<%= stash->{id} %>';
-  const ADD_GROUPS_URL  = '/api/meets/<%= stash->{id} %>/group';
-  const ADD_USERS_URL   = '/api/meets/<%= stash->{id} %>/moderator';
+  const ADD_GROUPS_URL  = '/api/meets/<%= stash->{id} %>/groups';
+  const ADD_USERS_URL   = '/api/meets/<%= stash->{id} %>/users';
   const API_HEADERS     = {
       "Content-Type": "application/json",
       "Accept": "application/json",
@@ -45,20 +48,28 @@
 
   createApp({
 
-    title: '',
     meet: {},
-    users: {},
 
     active_tab: 'groups',
     delete_confirm_visible: false,
 
-    search_groups: '',
-    search_users: '',
+    filter: {
+        group: '',
+        participant: '',
+        moderator: '',
+    },
+
+    search: {
+        group: '',
+        participant: '',
+        moderator: '',
+    },
 
-    groups: [],
-    users: [],
-    selected_groups: [],
-    selected_users: [],
+    selected: {
+        group: [],
+        participant: [],
+        moderator: [],
+    },
 
     tabClass(id) {
         if (id == this.active_tab) {
@@ -80,6 +91,31 @@
         })
     },
 
+    searchUsers(kind) {
+      if ( this.filter[kind].length < 2 ) {
+        this.search[kind] = [];
+        return true;
+      }
+
+      fetch(USERS_URL + '?search=' + this.filter[kind] )
+        .then((res) => res.json())
+        .then(res => {
+            this.search[kind] = res;
+        })
+    },
+
+    searchGroups() {
+      if ( this.filter['group'].length < 2 ) {
+        this.search['group'] = [];
+        return true;
+      }
+
+      fetch(GROUPS_URL + '?search=' + this.filter['group'] )
+        .then((res) => res.json())
+        .then(res => {
+            this.search['group'] = res;
+        })
+    },
 % if ( $roles->{owner} ) {
 
     updateMeet() {
@@ -94,19 +130,6 @@
         .then()
     },
 
-    searchUsers() {
-      if ( this.search_users.length < 2 ) {
-        this.users = [];
-        return true;
-      }
-
-      fetch(USERS_URL + '?search=' + this.search_users )
-        .then((res) => res.json())
-        .then(res => {
-            this.users = res;
-        })
-    },
-
     deleteMeet() {
       fetch(MEET_URL, {
         method: "DELETE",
@@ -120,29 +143,32 @@
         });
     },
 
-    addModerators() {
-      if ( this.selected_users.length == 0) {
+% }
+% if ( $roles->{owner} || $roles->{moderator} ) {
+
+    addUsers(kind) {
+      if ( this.selected[kind].length == 0) {
         return true;
       }
 
-      const selected = this.users.filter(item => this.selected_users.includes(item.id))
+      const selected = this.search[kind].filter(item => this.selected[kind].includes(item.id))
 
       fetch(ADD_USERS_URL, {
         method: "POST",
         headers: API_HEADERS,
-        body: JSON.stringify({ users: selected }),
+        body: JSON.stringify({ users: this.selected[kind], kind: kind }),
       })
       .then((response) => {
         if (response.ok) {
-            this.selected_users =[]
-            this.search_users = ''
+            this.selected[kind] =[]
+            this.filter[kind] = ''
             this.getMeet()
         }
       })
     },
 
-    removeModerator(id) {
-      fetch(MEET_URL + '/moderator/' + id, {
+    removeUser(id, kind) {
+      fetch(MEET_URL + '/users/' + id, {
         method: "DELETE",
         headers: API_HEADERS,
       })
@@ -153,43 +179,29 @@
       })
     },
 
-% }
-
-% if ( $roles->{owner} || $roles->{moderator} ) {
-    searchGroups() {
-      if ( this.search_groups.length < 2 ) {
-        this.groups = [];
-        return true;
-      }
 
-      fetch(GROUPS_URL + '?search=' + this.search_groups )
-        .then((res) => res.json())
-        .then(res => {
-            this.groups = res;
-        })
-    },
 
     addGroups() {
-      if ( this.selected_groups.length == 0) {
+      if ( this.selected['group'].length == 0) {
         return true;
       }
 
       fetch(ADD_GROUPS_URL, {
         method: "POST",
         headers: API_HEADERS,
-        body: JSON.stringify({ groups: this.selected_groups }),
+        body: JSON.stringify({ groups: this.selected['group'] }),
       })
       .then((response) => {
         if (response.ok) {
-            this.selected_groups =[]
-            this.search_groups = ''
+            this.selected['group'] =[]
+            this.filter['group'] = ''
             this.getMeet()
         }
       })
     },
 
     removeGroup(id) {
-      fetch(MEET_URL + '/group/' + id, {
+      fetch(MEET_URL + '/groups/' + id, {
         method: "DELETE",
         headers: API_HEADERS,
       })
-- 
GitLab