Merge pull request #5902 from FinnStutzenstein/ChatAccessGroups
Change chat access groups
This commit is contained in:
commit
baad950698
6
Makefile
6
Makefile
@ -9,8 +9,10 @@ run-dev: | build-dev
|
|||||||
stop-dev:
|
stop-dev:
|
||||||
docker-compose -f docker/docker-compose.dev.yml down
|
docker-compose -f docker/docker-compose.dev.yml down
|
||||||
|
|
||||||
get-server-shell:
|
server-shell:
|
||||||
docker-compose -f docker/docker-compose.dev.yml run server bash
|
docker-compose -f docker/docker-compose.dev.yml run --entrypoint="" server docker/wait-for-dev-dependencies.sh
|
||||||
|
UID=$$(id -u $${USER}) GID=$$(id -g $${USER}) docker-compose -f docker/docker-compose.dev.yml run --entrypoint="" server bash
|
||||||
|
docker-compose -f docker/docker-compose.dev.yml down
|
||||||
|
|
||||||
reload-proxy:
|
reload-proxy:
|
||||||
docker-compose -f docker/docker-compose.dev.yml exec -w /etc/caddy proxy caddy reload
|
docker-compose -f docker/docker-compose.dev.yml exec -w /etc/caddy proxy caddy reload
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 0197c762d94c0723b377b0b2773fb329b7ccaeca
|
Subproject commit 76d3f5385e0a76e79d09d573041a0839d8f483d0
|
@ -18,8 +18,14 @@ import { DataStoreService } from '../../core-services/data-store.service';
|
|||||||
const ChatGroupRelations: RelationDefinition[] = [
|
const ChatGroupRelations: RelationDefinition[] = [
|
||||||
{
|
{
|
||||||
type: 'M2M',
|
type: 'M2M',
|
||||||
ownIdKey: 'access_groups_id',
|
ownIdKey: 'read_groups_id',
|
||||||
ownKey: 'access_groups',
|
ownKey: 'read_groups',
|
||||||
|
foreignViewModel: ViewGroup
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2M',
|
||||||
|
ownIdKey: 'write_groups_id',
|
||||||
|
ownKey: 'write_groups',
|
||||||
foreignViewModel: ViewGroup
|
foreignViewModel: ViewGroup
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||||
|
|
||||||
|
const fadeSpeed = {
|
||||||
|
fast: 200,
|
||||||
|
slow: 600
|
||||||
|
};
|
||||||
|
|
||||||
const slideIn = [style({ transform: 'translateX(-85%)' }), animate('600ms ease')];
|
const slideIn = [style({ transform: 'translateX(-85%)' }), animate('600ms ease')];
|
||||||
const slideOut = [
|
const slideOut = [
|
||||||
style({ transform: 'translateX(0)' }),
|
style({ transform: 'translateX(0)' }),
|
||||||
@ -11,9 +16,15 @@ const slideOut = [
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const collapseAndFade = trigger('collapse', [
|
||||||
|
state('in', style({ opacity: 1, height: '100%' })),
|
||||||
|
transition(':enter', [style({ opacity: 0, height: 0 }), animate(fadeSpeed.fast)]),
|
||||||
|
transition(':leave', animate(fadeSpeed.fast, style({ opacity: 0, height: 0 })))
|
||||||
|
]);
|
||||||
|
|
||||||
export const fadeAnimation = trigger('fade', [
|
export const fadeAnimation = trigger('fade', [
|
||||||
state('in', style({ opacity: 1 })),
|
state('in', style({ opacity: 1 })),
|
||||||
transition(':enter', [style({ opacity: 0 }), animate(600)]),
|
transition(':enter', [style({ opacity: 0 }), animate(fadeSpeed.slow)]),
|
||||||
transition(':leave', animate(600, style({ opacity: 0 })))
|
transition(':leave', animate(fadeSpeed.slow, style({ opacity: 0 })))
|
||||||
]);
|
]);
|
||||||
export const navItemAnim = trigger('navItemAnim', [transition(':enter', slideIn), transition(':leave', slideOut)]);
|
export const navItemAnim = trigger('navItemAnim', [transition(':enter', slideIn), transition(':leave', slideOut)]);
|
||||||
|
@ -18,10 +18,6 @@ os-icon-container {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.small-container {
|
&.small-container {
|
||||||
@include icon-container(12px, 12px, 400);
|
@include icon-container(12px, 12px, 400);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ export class ChatGroup extends BaseModel<ChatGroup> {
|
|||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public name: string;
|
public name: string;
|
||||||
public access_groups_id: number[];
|
public read_groups_id: number[];
|
||||||
|
public write_groups_id: number[];
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super(ChatGroup.COLLECTIONSTRING, input);
|
super(ChatGroup.COLLECTIONSTRING, input);
|
||||||
|
@ -1,17 +1,29 @@
|
|||||||
<div class="chat-header" *osPerms="permission.chatCanManage">
|
<div class="chat-header" *osPerms="permission.chatCanManage">
|
||||||
<os-icon-container
|
<os-icon-container
|
||||||
icon="group"
|
icon="group"
|
||||||
*ngIf="chatGroup.access_groups.length"
|
*ngIf="chatGroup.write_groups.length"
|
||||||
matTooltip="{{ 'Access groups' | translate }}"
|
matTooltip="{{ 'Groups with write permissions' | translate }}"
|
||||||
>
|
>
|
||||||
<span *ngFor="let group of chatGroup.access_groups | slice: 0:3; let last = last">
|
<span *ngFor="let group of chatGroup.write_groups | slice: 0:3; let last = last">
|
||||||
<span>{{ group.getTitle() | translate }}</span>
|
<span>{{ group.getTitle() | translate }}</span>
|
||||||
<span *ngIf="!last">, </span>
|
<span *ngIf="!last">, </span>
|
||||||
<span *ngIf="last && chatGroup.access_groups.length > 3">...</span>
|
<span *ngIf="last && chatGroup.write_groups.length > 3">...</span>
|
||||||
|
</span>
|
||||||
|
</os-icon-container>
|
||||||
|
|
||||||
|
<os-icon-container
|
||||||
|
icon="remove_red_eye"
|
||||||
|
*ngIf="readOnlyGroups.length"
|
||||||
|
matTooltip="{{ 'Groups with read permissions' | translate }}"
|
||||||
|
>
|
||||||
|
<span *ngFor="let group of readOnlyGroups | slice: 0:3; let last = last">
|
||||||
|
<span>{{ group.getTitle() | translate }}</span>
|
||||||
|
<span *ngIf="!last">, </span>
|
||||||
|
<span *ngIf="last && chatGroup.read_groups.length > 3">...</span>
|
||||||
</span>
|
</span>
|
||||||
</os-icon-container>
|
</os-icon-container>
|
||||||
<button class="chat-options" mat-icon-button [matMenuTriggerFor]="chatgroupMenu">
|
<button class="chat-options" mat-icon-button [matMenuTriggerFor]="chatgroupMenu">
|
||||||
<mat-icon> more_vert </mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -41,5 +53,4 @@
|
|||||||
<span>{{ 'Delete' | translate }}</span>
|
<span>{{ 'Delete' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- edit -->
|
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
@ -22,6 +22,7 @@ import { ChatGroup } from 'app/shared/models/chat/chat-group';
|
|||||||
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import { ChatNotificationService } from 'app/site/chat/services/chat-notification.service';
|
import { ChatNotificationService } from 'app/site/chat/services/chat-notification.service';
|
||||||
|
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||||
import {
|
import {
|
||||||
ChatGroupData,
|
ChatGroupData,
|
||||||
EditChatGroupDialogComponent
|
EditChatGroupDialogComponent
|
||||||
@ -51,6 +52,12 @@ export class ChatGroupDetailComponent extends BaseViewComponentDirective impleme
|
|||||||
return isOnBottom;
|
return isOnBottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get readOnlyGroups(): ViewGroup[] {
|
||||||
|
const readGroups = this.chatGroup?.read_groups;
|
||||||
|
const writeGrous = this.chatGroup?.write_groups;
|
||||||
|
return readGroups?.filter(group => !writeGrous.includes(group)) || [];
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
@ -111,7 +118,8 @@ export class ChatGroupDetailComponent extends BaseViewComponentDirective impleme
|
|||||||
public editChat(): void {
|
public editChat(): void {
|
||||||
const chatData: ChatGroupData = {
|
const chatData: ChatGroupData = {
|
||||||
name: this.chatGroup.name,
|
name: this.chatGroup.name,
|
||||||
access_groups_id: this.chatGroup.access_groups_id
|
read_groups_id: this.chatGroup.read_groups_id,
|
||||||
|
write_groups_id: this.chatGroup.write_groups_id
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(EditChatGroupDialogComponent, {
|
const dialogRef = this.dialog.open(EditChatGroupDialogComponent, {
|
||||||
|
@ -4,8 +4,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="chat-text"
|
class="chat-text"
|
||||||
[ngClass]="{ 'background-primary': !isOwnMessage, 'background-primary-darkest': isOwnMessage }"
|
[ngClass]="{
|
||||||
[matMenuTriggerFor]="canDelete ? singleChatMenu : null"
|
disabled: !canDelete,
|
||||||
|
'background-primary': !isOwnMessage,
|
||||||
|
'background-primary-darkest': isOwnMessage
|
||||||
|
}"
|
||||||
|
[matMenuTriggerFor]="singleChatMenu"
|
||||||
>
|
>
|
||||||
{{ text }}
|
{{ text }}
|
||||||
<div class="timestamp">{{ date | localizedDate }}</div>
|
<div class="timestamp">{{ date | localizedDate }}</div>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<mat-tab-group (selectedTabChange)="selectedTabChange($event)" *ngIf="chatGroupsExist()">
|
<mat-tab-group (selectedTabChange)="selectedTabChange($event)" *ngIf="chatGroupsExist">
|
||||||
<mat-tab *ngFor="let chat of chatGroupSubject | async">
|
<mat-tab *ngFor="let chat of chatGroupSubject | async">
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
<span
|
<span
|
||||||
[matBadgeHidden]="!getNotidficationsForChatId(chat.id)"
|
[matBadgeHidden]="!getNotificationsForChatId(chat.id)"
|
||||||
[matBadge]="getNotidficationsForChatId(chat.id)"
|
[matBadge]="getNotificationsForChatId(chat.id)"
|
||||||
matBadgeColor="accent"
|
matBadgeColor="accent"
|
||||||
matBadgeOverlap="false"
|
matBadgeOverlap="false"
|
||||||
>
|
>
|
||||||
@ -16,14 +16,14 @@
|
|||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
|
|
||||||
<div *ngIf="!chatGroupsExist()">
|
<div *ngIf="!chatGroupsExist">
|
||||||
<span>
|
<span>
|
||||||
{{ 'No chat groups available' | translate }}
|
{{ 'No chat groups available' | translate }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- send chat -->
|
<!-- send chat -->
|
||||||
<form [formGroup]="newMessageForm" (ngSubmit)="send()" *ngIf="chatGroupsExist()">
|
<form [formGroup]="newMessageForm" (ngSubmit)="send()" *ngIf="chatGroupsExist && canSendInSelectedChat" [@collapse]>
|
||||||
<mat-form-field appearance="outline" class="chat-form-field">
|
<mat-form-field appearance="outline" class="chat-form-field">
|
||||||
<input class="chat-input" type="text" matInput formControlName="text" />
|
<input class="chat-input" type="text" matInput formControlName="text" />
|
||||||
<mat-label>{{ 'Message' | translate }}</mat-label>
|
<mat-label>{{ 'Message' | translate }}</mat-label>
|
||||||
|
@ -7,8 +7,10 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { ChatGroupRepositoryService } from 'app/core/repositories/chat/chat-group-repository.service';
|
import { ChatGroupRepositoryService } from 'app/core/repositories/chat/chat-group-repository.service';
|
||||||
import { ChatMessageRepositoryService } from 'app/core/repositories/chat/chat-message-repository.service';
|
import { ChatMessageRepositoryService } from 'app/core/repositories/chat/chat-message-repository.service';
|
||||||
|
import { collapseAndFade } from 'app/shared/animations';
|
||||||
import { ChatMessage } from 'app/shared/models/chat/chat-message';
|
import { ChatMessage } from 'app/shared/models/chat/chat-message';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import { ChatNotificationService, NotificationAmount } from '../../services/chat-notification.service';
|
import { ChatNotificationService, NotificationAmount } from '../../services/chat-notification.service';
|
||||||
@ -18,7 +20,8 @@ import { ViewChatGroup } from '../../models/view-chat-group';
|
|||||||
selector: 'os-chat-tabs',
|
selector: 'os-chat-tabs',
|
||||||
templateUrl: './chat-tabs.component.html',
|
templateUrl: './chat-tabs.component.html',
|
||||||
styleUrls: ['./chat-tabs.component.scss'],
|
styleUrls: ['./chat-tabs.component.scss'],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None,
|
||||||
|
animations: [collapseAndFade]
|
||||||
})
|
})
|
||||||
export class ChatTabsComponent extends BaseViewComponentDirective implements OnInit {
|
export class ChatTabsComponent extends BaseViewComponentDirective implements OnInit {
|
||||||
public chatGroupSubject: BehaviorSubject<ViewChatGroup[]>;
|
public chatGroupSubject: BehaviorSubject<ViewChatGroup[]>;
|
||||||
@ -27,6 +30,21 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
|
|||||||
|
|
||||||
private notifications: NotificationAmount;
|
private notifications: NotificationAmount;
|
||||||
|
|
||||||
|
private get chatGroupFromIndex(): ViewChatGroup {
|
||||||
|
return this.chatGroupSubject.value[this.selectedTabIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get chatGroupsExist(): boolean {
|
||||||
|
return this.chatGroupSubject.value.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get canSendInSelectedChat(): boolean {
|
||||||
|
if (!this.chatGroupFromIndex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.operator.isInGroupIds(...this.chatGroupFromIndex?.write_groups_id) || false;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
titleService: Title,
|
titleService: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
@ -34,6 +52,7 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
|
|||||||
private repo: ChatGroupRepositoryService,
|
private repo: ChatGroupRepositoryService,
|
||||||
private chatMessageRepo: ChatMessageRepositoryService,
|
private chatMessageRepo: ChatMessageRepositoryService,
|
||||||
private chatNotificationService: ChatNotificationService,
|
private chatNotificationService: ChatNotificationService,
|
||||||
|
private operator: OperatorService,
|
||||||
formBuilder: FormBuilder
|
formBuilder: FormBuilder
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
@ -55,14 +74,10 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
|
|||||||
this.selectedTabIndex = event.index;
|
this.selectedTabIndex = event.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNotidficationsForChatId(chatId: number): number {
|
public getNotificationsForChatId(chatId: number): number {
|
||||||
return this.notifications?.[chatId] ?? 0;
|
return this.notifications?.[chatId] ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public chatGroupsExist(): boolean {
|
|
||||||
return this.chatGroupSubject.value.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isChatMessageEmpty(): boolean {
|
public isChatMessageEmpty(): boolean {
|
||||||
return !this.newMessageForm?.value?.text?.trim();
|
return !this.newMessageForm?.value?.text?.trim();
|
||||||
}
|
}
|
||||||
@ -70,7 +85,7 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
|
|||||||
public send(): void {
|
public send(): void {
|
||||||
const payload = {
|
const payload = {
|
||||||
text: this.newMessageForm.value.text,
|
text: this.newMessageForm.value.text,
|
||||||
chatgroup_id: this.chatGroupSubject.value[this.selectedTabIndex].id
|
chatgroup_id: this.chatGroupFromIndex.id
|
||||||
};
|
};
|
||||||
this.chatMessageRepo
|
this.chatMessageRepo
|
||||||
.create(payload as ChatMessage)
|
.create(payload as ChatMessage)
|
||||||
|
@ -24,9 +24,17 @@
|
|||||||
<!-- Groups -->
|
<!-- Groups -->
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
formControlName="access_groups_id"
|
formControlName="read_groups_id"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
placeholder="{{ 'Access groups' | translate }}"
|
placeholder="{{ 'Groups with read permissions' | translate }}"
|
||||||
|
[inputListValues]="groupsBehaviorSubject"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<os-search-value-selector
|
||||||
|
formControlName="write_groups_id"
|
||||||
|
[multiple]="true"
|
||||||
|
placeholder="{{ 'Groups with write permissions' | translate }}"
|
||||||
[inputListValues]="groupsBehaviorSubject"
|
[inputListValues]="groupsBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -10,7 +10,8 @@ import { ViewGroup } from 'app/site/users/models/view-group';
|
|||||||
|
|
||||||
export interface ChatGroupData {
|
export interface ChatGroupData {
|
||||||
name: string;
|
name: string;
|
||||||
access_groups_id: number[];
|
read_groups_id: number[];
|
||||||
|
write_groups_id: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -36,7 +37,8 @@ export class EditChatGroupDialogComponent {
|
|||||||
this.createMode = !data;
|
this.createMode = !data;
|
||||||
this.createUpdateForm = formBuilder.group({
|
this.createUpdateForm = formBuilder.group({
|
||||||
name: [data?.name || '', Validators.required],
|
name: [data?.name || '', Validators.required],
|
||||||
access_groups_id: [data?.access_groups_id || []]
|
read_groups_id: [data?.read_groups_id || []],
|
||||||
|
write_groups_id: [data?.write_groups_id || []]
|
||||||
});
|
});
|
||||||
this.groupsBehaviorSubject = groupRepo.getViewModelListBehaviorSubject();
|
this.groupsBehaviorSubject = groupRepo.getViewModelListBehaviorSubject();
|
||||||
}
|
}
|
||||||
|
@ -15,5 +15,6 @@ export class ViewChatGroup extends BaseViewModel<ChatGroup> implements ChatGroup
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface ViewChatGroup extends ChatGroup {
|
export interface ViewChatGroup extends ChatGroup {
|
||||||
access_groups: ViewGroup[];
|
read_groups: ViewGroup[];
|
||||||
|
write_groups: ViewGroup[];
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,9 @@ export class ChatService {
|
|||||||
private canSeeSomeChatGroup = false;
|
private canSeeSomeChatGroup = false;
|
||||||
private canManage = false;
|
private canManage = false;
|
||||||
|
|
||||||
private canSeeChat = new BehaviorSubject<boolean>(false);
|
private canSeeChatSubject = new BehaviorSubject<boolean>(false);
|
||||||
public get canSeeChatObservable(): Observable<boolean> {
|
public get canSeeChatObservable(): Observable<boolean> {
|
||||||
return this.canSeeChat.asObservable();
|
return this.canSeeChatSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
@ -34,7 +34,7 @@ export class ChatService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.repo.getViewModelListBehaviorSubject().subscribe(groups => {
|
this.repo.getViewModelListBehaviorSubject().subscribe(groups => {
|
||||||
this.canSeeSomeChatGroup = !!groups && groups.length > 0;
|
this.canSeeSomeChatGroup = groups?.length > 0;
|
||||||
this.update();
|
this.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,6 +45,6 @@ export class ChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private update(): void {
|
private update(): void {
|
||||||
this.canSeeChat.next(this.chatEnabled && (this.canSeeSomeChatGroup || this.canManage));
|
this.canSeeChatSubject.next(this.chatEnabled && (this.canSeeSomeChatGroup || this.canManage));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ services:
|
|||||||
- ../server:/app
|
- ../server:/app
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
|
- postgres
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:11
|
image: postgres:11
|
||||||
|
@ -19,12 +19,7 @@ function isSettingsFileOk() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
wait-for-it -t 0 redis:6379
|
/app/docker/wait-for-dev-dependencies.sh
|
||||||
|
|
||||||
until pg_isready -h postgres -p 5432 -U openslides; do
|
|
||||||
echo "Waiting for Postgres to become available..."
|
|
||||||
sleep 3
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ! -f "/app/personal_data/var/settings.py" ]]; then
|
if [[ ! -f "/app/personal_data/var/settings.py" ]]; then
|
||||||
echo "Create settings"
|
echo "Create settings"
|
||||||
|
10
server/docker/wait-for-dev-dependencies.sh
Executable file
10
server/docker/wait-for-dev-dependencies.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
wait-for-it -t 0 redis:6379
|
||||||
|
|
||||||
|
until pg_isready -h postgres -p 5432 -U openslides; do
|
||||||
|
echo "Waiting for Postgres to become available..."
|
||||||
|
sleep 3
|
||||||
|
done
|
0
server/manage.py
Executable file → Normal file
0
server/manage.py
Executable file → Normal file
@ -7,7 +7,7 @@ from openslides.utils.auth import async_has_perm, async_in_some_groups
|
|||||||
class ChatGroupAccessPermissions(BaseAccessPermissions):
|
class ChatGroupAccessPermissions(BaseAccessPermissions):
|
||||||
"""
|
"""
|
||||||
Access permissions container for ChatGroup and ChatGroupViewSet.
|
Access permissions container for ChatGroup and ChatGroupViewSet.
|
||||||
No base perm: The access permissions are done with the access groups
|
No base perm: The access permissions are done with the read/write groups.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def get_restricted_data(
|
async def get_restricted_data(
|
||||||
@ -22,10 +22,11 @@ class ChatGroupAccessPermissions(BaseAccessPermissions):
|
|||||||
data = full_data
|
data = full_data
|
||||||
else:
|
else:
|
||||||
for full in full_data:
|
for full in full_data:
|
||||||
access_groups = full.get("access_groups_id", [])
|
read_groups = full.get("read_groups_id", [])
|
||||||
if len(
|
write_groups = full.get("write_groups_id", [])
|
||||||
full.get("access_groups_id", [])
|
if await async_in_some_groups(
|
||||||
) == 0 or await async_in_some_groups(user_id, access_groups):
|
user_id, read_groups
|
||||||
|
) or await async_in_some_groups(user_id, write_groups):
|
||||||
data.append(full)
|
data.append(full)
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Finn Stutzenstein on 2021-02-18 08:12
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("chat", "0001_initial"),
|
||||||
|
("users", "0016_remove_user_ordering"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="chatgroup",
|
||||||
|
old_name="access_groups",
|
||||||
|
new_name="read_groups",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="chatgroup",
|
||||||
|
name="read_groups",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="chat_read_groups", to="users.Group"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="chatgroup",
|
||||||
|
name="write_groups",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="chat_write_groups", to="users.Group"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Finn Stutzenstein on 2021-02-18 08:12
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def copy_read_groups_to_write_groups(apps, schema_editor):
|
||||||
|
ChatGroup = apps.get_model("chat", "ChatGroup")
|
||||||
|
|
||||||
|
for chatgroup in ChatGroup.objects.all():
|
||||||
|
chatgroup.write_groups.add(*chatgroup.read_groups.all())
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("chat", "0002_new_access_groups_1"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [migrations.RunPython(copy_read_groups_to_write_groups)]
|
@ -18,7 +18,7 @@ class ChatGroupManager(BaseManager):
|
|||||||
return (
|
return (
|
||||||
super()
|
super()
|
||||||
.get_prefetched_queryset(*args, **kwargs)
|
.get_prefetched_queryset(*args, **kwargs)
|
||||||
.prefetch_related("access_groups")
|
.prefetch_related("read_groups", "write_groups")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -30,8 +30,11 @@ class ChatGroup(RESTModelMixin, models.Model):
|
|||||||
objects = ChatGroupManager()
|
objects = ChatGroupManager()
|
||||||
|
|
||||||
name = models.CharField(max_length=256)
|
name = models.CharField(max_length=256)
|
||||||
access_groups = models.ManyToManyField(
|
read_groups = models.ManyToManyField(
|
||||||
settings.AUTH_GROUP_MODEL, blank=True, related_name="chat_access_groups"
|
settings.AUTH_GROUP_MODEL, blank=True, related_name="chat_read_groups"
|
||||||
|
)
|
||||||
|
write_groups = models.ManyToManyField(
|
||||||
|
settings.AUTH_GROUP_MODEL, blank=True, related_name="chat_write_groups"
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -41,14 +44,11 @@ class ChatGroup(RESTModelMixin, models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def can_access(self, user):
|
def can_write(self, user):
|
||||||
if has_perm(user.id, "chat.can_manage"):
|
if has_perm(user.id, "chat.can_manage"):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if not self.access_groups.exists():
|
return in_some_groups(user.id, self.write_groups.values_list(flat=True))
|
||||||
return True
|
|
||||||
|
|
||||||
return in_some_groups(user.id, self.access_groups.values_list(flat=True))
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessageManager(BaseManager):
|
class ChatMessageManager(BaseManager):
|
||||||
@ -61,7 +61,9 @@ class ChatMessageManager(BaseManager):
|
|||||||
return (
|
return (
|
||||||
super()
|
super()
|
||||||
.get_prefetched_queryset(*args, **kwargs)
|
.get_prefetched_queryset(*args, **kwargs)
|
||||||
.prefetch_related("chatgroup", "chatgroup__access_groups")
|
.prefetch_related(
|
||||||
|
"chatgroup", "chatgroup__read_groups", "chatgroup__write_groups"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,10 @@ class ChatGroupSerializer(ModelSerializer):
|
|||||||
Serializer for chat.models.ChatGroup objects.
|
Serializer for chat.models.ChatGroup objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
access_groups = IdPrimaryKeyRelatedField(
|
read_groups = IdPrimaryKeyRelatedField(
|
||||||
|
many=True, required=False, queryset=get_group_model().objects.all()
|
||||||
|
)
|
||||||
|
write_groups = IdPrimaryKeyRelatedField(
|
||||||
many=True, required=False, queryset=get_group_model().objects.all()
|
many=True, required=False, queryset=get_group_model().objects.all()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,7 +26,8 @@ class ChatGroupSerializer(ModelSerializer):
|
|||||||
fields = (
|
fields = (
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"access_groups",
|
"read_groups",
|
||||||
|
"write_groups",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -35,7 +39,8 @@ class ChatMessageSerializer(ModelSerializer):
|
|||||||
chatgroup = IdPrimaryKeyRelatedField(
|
chatgroup = IdPrimaryKeyRelatedField(
|
||||||
required=False, queryset=ChatGroup.objects.all()
|
required=False, queryset=ChatGroup.objects.all()
|
||||||
)
|
)
|
||||||
access_groups_id = SerializerMethodField()
|
read_groups_id = SerializerMethodField()
|
||||||
|
write_groups_id = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ChatMessage
|
model = ChatMessage
|
||||||
@ -46,7 +51,8 @@ class ChatMessageSerializer(ModelSerializer):
|
|||||||
"timestamp",
|
"timestamp",
|
||||||
"username",
|
"username",
|
||||||
"user_id",
|
"user_id",
|
||||||
"access_groups_id",
|
"read_groups_id",
|
||||||
|
"write_groups_id",
|
||||||
)
|
)
|
||||||
read_only_fields = (
|
read_only_fields = (
|
||||||
"username",
|
"username",
|
||||||
@ -58,5 +64,8 @@ class ChatMessageSerializer(ModelSerializer):
|
|||||||
data["text"] = validate_html_strict(data["text"])
|
data["text"] = validate_html_strict(data["text"])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def get_access_groups_id(self, chatmessage):
|
def get_read_groups_id(self, chatmessage):
|
||||||
return [group.id for group in chatmessage.chatgroup.access_groups.all()]
|
return [group.id for group in chatmessage.chatgroup.read_groups.all()]
|
||||||
|
|
||||||
|
def get_write_groups_id(self, chatmessage):
|
||||||
|
return [group.id for group in chatmessage.chatgroup.write_groups.all()]
|
||||||
|
@ -51,8 +51,8 @@ class ChatGroupViewSet(ModelViewSet):
|
|||||||
|
|
||||||
def update(self, *args, **kwargs):
|
def update(self, *args, **kwargs):
|
||||||
response = super().update(*args, **kwargs)
|
response = super().update(*args, **kwargs)
|
||||||
# Update all affected chatmessages to update their `access_groups_id` field,
|
# Update all affected chatmessages to update their `read_groups_id` and
|
||||||
# which is taken from the updated chatgroup.
|
# `write_groups_id` field, which is taken from the updated chatgroup.
|
||||||
inform_changed_data(ChatMessage.objects.filter(chatgroup=self.get_object()))
|
inform_changed_data(ChatMessage.objects.filter(chatgroup=self.get_object()))
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class ChatMessageViewSet(
|
|||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
if not serializer.validated_data["chatgroup"].can_access(self.request.user):
|
if not serializer.validated_data["chatgroup"].can_write(self.request.user):
|
||||||
self.permission_denied(self.request)
|
self.permission_denied(self.request)
|
||||||
|
|
||||||
# Do not use the serializer.save since it will put the model in the history.
|
# Do not use the serializer.save since it will put the model in the history.
|
||||||
|
@ -22,6 +22,7 @@ from django.http.request import QueryDict
|
|||||||
from django.utils.encoding import force_bytes, force_text
|
from django.utils.encoding import force_bytes, force_text
|
||||||
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
|
||||||
|
|
||||||
|
from openslides.chat.models import ChatGroup
|
||||||
from openslides.saml import SAML_ENABLED
|
from openslides.saml import SAML_ENABLED
|
||||||
from openslides.utils import logging
|
from openslides.utils import logging
|
||||||
|
|
||||||
@ -229,6 +230,7 @@ class UserViewSet(ModelViewSet):
|
|||||||
old_delegation_user.save()
|
old_delegation_user.save()
|
||||||
|
|
||||||
inform_changed_data(user)
|
inform_changed_data(user)
|
||||||
|
inform_changed_data(ChatGroup.objects.all())
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def assert_vote_not_delegated(self, user):
|
def assert_vote_not_delegated(self, user):
|
||||||
|
@ -10,19 +10,24 @@ def test_motion_db_queries():
|
|||||||
"""
|
"""
|
||||||
Tests that only the following db queries for chat groups are done:
|
Tests that only the following db queries for chat groups are done:
|
||||||
* 1 request to get all chat groups
|
* 1 request to get all chat groups
|
||||||
* 1 request to get all access groups
|
* 1 request to get all read groups
|
||||||
|
* 1 request to get all write groups
|
||||||
|
|
||||||
Tests that only the following db queries for chat messages are done:
|
Tests that only the following db queries for chat messages are done:
|
||||||
* 1 request to fet all chat messages
|
* 1 request to fet all chat messages
|
||||||
* 1 request to get all chat groups
|
* 1 request to get all chat groups
|
||||||
* 1 request to get all access groups
|
* 1 request to get all read groups
|
||||||
|
* 1 request to get all write groups
|
||||||
"""
|
"""
|
||||||
group1 = get_group_model().objects.create(name="group1")
|
group1 = get_group_model().objects.create(name="group1")
|
||||||
group2 = get_group_model().objects.create(name="group2")
|
group2 = get_group_model().objects.create(name="group2")
|
||||||
|
group3 = get_group_model().objects.create(name="group3")
|
||||||
|
group4 = get_group_model().objects.create(name="group4")
|
||||||
|
|
||||||
for i1 in range(5):
|
for i1 in range(5):
|
||||||
chatgroup = ChatGroup.objects.create(name=f"motion{i1}")
|
chatgroup = ChatGroup.objects.create(name=f"motion{i1}")
|
||||||
chatgroup.access_groups.add(group1, group2)
|
chatgroup.read_groups.add(group1, group2)
|
||||||
|
chatgroup.write_groups.add(group3, group4)
|
||||||
|
|
||||||
for i2 in range(10):
|
for i2 in range(10):
|
||||||
ChatMessage.objects.create(
|
ChatMessage.objects.create(
|
||||||
@ -32,5 +37,5 @@ def test_motion_db_queries():
|
|||||||
chatgroup=chatgroup,
|
chatgroup=chatgroup,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert count_queries(ChatGroup.get_elements)() == 2
|
assert count_queries(ChatGroup.get_elements)() == 3
|
||||||
assert count_queries(ChatMessage.get_elements)() == 3
|
assert count_queries(ChatMessage.get_elements)() == 4
|
||||||
|
Loading…
Reference in New Issue
Block a user