Skip to content
Snippets Groups Projects
Verified Commit b1dcaf9c authored by Andrej Ramašeuski's avatar Andrej Ramašeuski
Browse files

Refactoring, podpora hostu mimo skupiny

parent 91d52bcf
No related branches found
No related tags found
No related merge requests found
Pipeline #10550 passed
Showing with 287 additions and 131 deletions
......@@ -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},
......
......@@ -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,
}
);
......@@ -83,8 +93,7 @@ sub list($c) {
$meet->get_columns,
groups => $meet->cached_groups,
moderators => $meet->cached_moderators,
# is_owned => ( $meet->owner_id == $c->stash->{user}->id),
# is_moderated => 0,
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->delete_related('meet_groups',
{ group_id => $c->stash->{group_id} }
);
}
$meet->update_moderators_cache();
$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} }
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_groups_cache();
}
$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};
......
......@@ -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),
......
......@@ -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' }
);
}
}
......
......@@ -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',
);
......
......@@ -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 // {};
......
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;
package SeMeet::Schema::Result::Moderator;
package SeMeet::Schema::Result::MeetUser_view;
use strict;
use warnings;
use base 'DBIx::Class::Core';
use base 'SeMeet::Schema::Result::MeetUser';
our $VERSION = 1;
__PACKAGE__->table('moderators');
__PACKAGE__->table('meets_users_view');
__PACKAGE__->add_columns(
qw(
meet_id
octid
name
user_name
),
);
__PACKAGE__->set_primary_key('meet_id', 'octid');
1;
......@@ -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
......@@ -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")
;
......@@ -36,6 +36,6 @@
},
};
const jitsi = new JitsiMeetExternalAPI(MEET_DOMAIN, MEET_OPTIONS);
// const jitsi = new JitsiMeetExternalAPI(MEET_DOMAIN, MEET_OPTIONS);
</script>
......@@ -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>
......
......@@ -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>
......
<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>
% }
......@@ -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,
})
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment