Skip to content

Commit

Permalink
Don't fetch permissions unless actually used #10746
Browse files Browse the repository at this point in the history
To minimize the risk of future API breaking change that may happen in
the permissions query and would prevent opening the door without reason
  • Loading branch information
PowerKiKi committed Sep 28, 2024
1 parent 592f0ce commit c33db17
Show file tree
Hide file tree
Showing 15 changed files with 46 additions and 54 deletions.
2 changes: 1 addition & 1 deletion client/app/admin/events/events/events.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@
</div>
}

@if (permissionsService.crud?.event.create) {
@if ((permissionsService.crud | async)?.event.create) {
<natural-fixed-button icon="add" [routerLink]="['/admin/event/new']" />
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,6 @@
</div>
}

@if (permissionsService.crud?.facilitatorDocument.create) {
@if ((permissionsService.crud | async)?.facilitatorDocument.create) {
<natural-fixed-button icon="add" [routerLink]="['/admin/facilitator-document/new']" />
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {MatIconModule} from '@angular/material/icon';
import {MatTooltipModule} from '@angular/material/tooltip';
import {MatSortModule} from '@angular/material/sort';
import {MatTableModule} from '@angular/material/table';
import {AsyncPipe} from '@angular/common';

@Component({
selector: 'app-facilitator-documents',
Expand All @@ -34,6 +35,7 @@ import {MatTableModule} from '@angular/material/table';
MatPaginatorModule,
NaturalFixedButtonComponent,
RouterLink,
AsyncPipe,
],
})
export class FacilitatorDocumentsComponent extends NaturalAbstractList<FacilitatorDocumentsService> implements OnInit {
Expand Down
2 changes: 1 addition & 1 deletion client/app/admin/newses/newses/newses.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,6 @@
</div>
}

@if (permissionsService.crud?.news.create) {
@if ((permissionsService.crud | async)?.news.create) {
<natural-fixed-button icon="add" [routerLink]="['/admin/news/new']" />
}
2 changes: 1 addition & 1 deletion client/app/admin/products/products/products.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,6 @@
</div>
}

@if (permissionsService.crud?.product.create) {
@if ((permissionsService.crud | async)?.product.create) {
<natural-fixed-button icon="add" [routerLink]="['/admin/product/new']" />
}
2 changes: 1 addition & 1 deletion client/app/admin/sessions/sessions/sessions.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@
</div>
}

@if (permissionsService.crud?.session.create) {
@if ((permissionsService.crud | async)?.session.create) {
<natural-fixed-button icon="add" [routerLink]="['/admin/session/new']" />
}
2 changes: 1 addition & 1 deletion client/app/admin/users/import/import.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ <h1 class="mat-headline-5">{{ routeData.seo.title }}</h1>
naturalFileDrop
accept=".csv"
[selectable]="true"
[disabled]="!permissionsService.crud?.organization.create || importing"
[disabled]="!(permissionsService.crud | async)?.organization.create || importing"
(filesChange)="uploadFile($event)"
>
<mat-icon naturalIcon="file_upload" />
Expand Down
2 changes: 2 additions & 0 deletions client/app/admin/users/import/import.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {UserService} from '../services/user.service';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatIconModule} from '@angular/material/icon';
import {MatButtonModule} from '@angular/material/button';
import {AsyncPipe} from '@angular/common';

@Component({
selector: 'app-import',
Expand All @@ -28,6 +29,7 @@ import {MatButtonModule} from '@angular/material/button';
NaturalIconDirective,
MatProgressSpinnerModule,
RouterLink,
AsyncPipe,
],
})
export class ImportComponent implements OnInit {
Expand Down
2 changes: 1 addition & 1 deletion client/app/admin/users/users/users.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,6 @@
</div>
}

@if (routeData?.isAdmin && permissionsService.crud?.user.create) {
@if (routeData?.isAdmin && (permissionsService.crud | async)?.user.create) {
<natural-fixed-button icon="add" [routerLink]="['/admin/user/new']" />
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="nat-vertical nat-gap-30">
@if (permissionsService.crud?.comment?.create) {
@if ((permissionsService.crud | async)?.comment?.create) {
<div class="nat-vertical nat-left">
<mat-form-field [style.width]="'100%'">
<mat-label>Rédiger un commentaire</mat-label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a mat-flat-button [routerLink]="form.get('buttonLink')?.value">
{{ form.get('buttonLabel')?.value }}
</a>
@if (permissionService.crud?.configuration.create) {
@if ((permissionsService.crud | async)?.configuration.create) {
<button
class="edit-button"
color="none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {NaturalIconDirective} from '@ecodev/natural';
import {MatIconModule} from '@angular/material/icon';
import {RouterLink} from '@angular/router';
import {MatButtonModule} from '@angular/material/button';
import {AsyncPipe} from '@angular/common';

type Block = {
title: string;
Expand All @@ -35,6 +36,7 @@ type Block = {
MatFormFieldModule,
MatInputModule,
TextFieldModule,
AsyncPipe,
],
})
export class HomeBlockComponent implements OnInit {
Expand All @@ -48,7 +50,7 @@ export class HomeBlockComponent implements OnInit {

public constructor(
private readonly configService: ConfigurationService,
public readonly permissionService: PermissionsService,
public readonly permissionsService: PermissionsService,
) {}

public ngOnInit(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
<div class="mat-headline-4 section-title nat-gap-30 nat-horizontal">
<div class="nat-expand">ACTUALITÉS</div>
<div class="relative">
@if (permissionService.crud?.news.create) {
@if ((permissionsService.crud | async)?.news.create) {
<button
class="absolute"
color="none"
Expand Down Expand Up @@ -57,7 +57,7 @@
<div class="mat-headline-4 section-title nat-gap-30 nat-horizontal">
<div class="nat-expand">AGENDA</div>
<div class="relative">
@if (permissionService.crud?.event.create) {
@if ((permissionsService.crud | async)?.event.create) {
<button
class="absolute"
color="none"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class HomepageComponent implements OnInit {
private readonly newsService: NewsService,
private readonly eventService: EventService,
private readonly productService: ProductService,
public readonly permissionService: PermissionsService,
public readonly permissionsService: PermissionsService,
) {}

public ngOnInit(): void {
Expand Down
68 changes: 27 additions & 41 deletions client/app/shared/services/permissions.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {Apollo} from 'apollo-angular';
import {Injectable} from '@angular/core';
import {Literal} from '@ecodev/natural';
import {isEqual} from 'lodash-es';
import {BehaviorSubject, Observable, of, ReplaySubject} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, skip, take} from 'rxjs/operators';
import {BehaviorSubject, Observable} from 'rxjs';
import {concatMap, debounceTime, distinctUntilChanged, filter, map, shareReplay} from 'rxjs/operators';
import {Permissions} from '../generated-types';
import {isEqual} from 'lodash-es';
import {permissionsQuery} from './permissions.queries';

type Contexts = {
Expand All @@ -13,66 +13,52 @@ type Contexts = {

/**
* A service to fetch permissions and use them in templates.
*
* Current site and state will be automatically taken into account when they change.
* The other contexts must be set manually, when available, to get correct permissions.
*/
@Injectable({
providedIn: 'root',
})
export class PermissionsService {
/**
* CRUD permissions, usually for object creations
* Observable of CRUD permissions, usually for object creations
*/
public crud: Permissions['permissions']['crud'] | null = null;
public readonly crud: Observable<Permissions['permissions']['crud']>;

/**
* Observable of changed permissions. Here we use a ReplaySubject so that new subscriber will get
* the most recent available permissions (useful in route guard)
* Observable of changed permissions
*/
public changes = new ReplaySubject<Permissions['permissions']>(1);
public readonly changes: Observable<Permissions['permissions']>;

private readonly currentContexts = new BehaviorSubject<Contexts>({
user: null,
});

public constructor(apollo: Apollo) {
// Query the API when our variables changed
this.currentContexts.pipe(distinctUntilChanged(isEqual), debounceTime(5)).subscribe(() => {
// Fetch global permissions
apollo
.query<Permissions>({
query: permissionsQuery,
})
.pipe(filter(result => !result.loading))
.subscribe(result => {
this.crud = result.data.permissions.crud;
this.changes.next(result.data.permissions);
});
});
}

public setUser(user: Literal | null): Observable<Permissions['permissions']> {
const newContexts = {
user: user ? user.id : null,
};
// Query the API whenever our variables change
const fetch = this.currentContexts.pipe(
distinctUntilChanged(isEqual),
debounceTime(5),
concatMap(() =>
apollo.query<Permissions>({query: permissionsQuery}).pipe(filter(result => !result.loading)),
),
shareReplay(), // new subscriber will get the most recent available permissions
);

return this.setNewContexts(newContexts);
this.crud = fetch.pipe(map(result => result.data.permissions.crud));
this.changes = fetch.pipe(map(result => result.data.permissions));
}

/**
* Return an observable that will complete as soon as the next permissions are available
*/
private setNewContexts(newContexts: Contexts): Observable<Permissions['permissions']> {
if (isEqual(this.currentContexts.value, newContexts) && this.crud) {
return of({
__typename: 'AllPermissions',
crud: this.crud,
});
} else {
this.currentContexts.next(newContexts);
private setNewContexts(newContexts: Contexts): void {
this.currentContexts.next(newContexts);
}

public setUser(user: Literal | null): void {
const newContexts = {
user: user ? user.id : null,
};

return this.changes.pipe(skip(1), take(1));
}
this.setNewContexts(newContexts);
}
}

0 comments on commit c33db17

Please sign in to comment.