Browse Source

Task/improve type safety in user account access component (#6934)

* feat(client): resolve errors

* feat(client): replace constructor based DI with inject function

* feat(client): enforce encapsulation

* feat(client): enforce immutability

* feat(client): replace deprecated getDeviceInfo

* fix(client): use AccessPermission enum to replace hard coded string
pull/6938/head
Kenrick Tandrian 1 day ago
committed by GitHub
parent
commit
0e15d14861
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 35
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  2. 2
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/interfaces/interfaces.ts
  3. 93
      apps/client/src/app/components/user-account-access/user-account-access.component.ts

35
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts

@ -29,6 +29,7 @@ import {
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select';
import { AccessPermission } from '@prisma/client';
import { StatusCodes } from 'http-status-codes';
import { EMPTY, catchError } from 'rxjs';
@ -52,7 +53,7 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
})
export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
protected accessForm: FormGroup;
protected mode: 'create' | 'update';
protected readonly mode: 'create' | 'update';
private readonly changeDetectorRef = inject(ChangeDetectorRef);
@ -69,21 +70,25 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
private readonly notificationService = inject(NotificationService);
public constructor() {
this.mode = this.data.access?.id ? 'update' : 'create';
this.mode = this.data.access ? 'update' : 'create';
}
public ngOnInit() {
const isPublic = this.data.access.type === 'PUBLIC';
const access = this.data?.access;
const isPublic = access?.type === 'PUBLIC';
this.accessForm = this.formBuilder.group({
alias: [this.data.access.alias],
alias: [access?.alias ?? ''],
granteeUserId: [
this.data.access.grantee,
access?.grantee ?? null,
isPublic ? null : Validators.required
],
permissions: [this.data.access.permissions[0], Validators.required],
permissions: [
access?.permissions[0] ?? AccessPermission.READ_RESTRICTED,
Validators.required
],
type: [
{ disabled: this.mode === 'update', value: this.data.access.type },
{ disabled: this.mode === 'update', value: access?.type ?? 'PRIVATE' },
Validators.required
]
});
@ -100,7 +105,9 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
} else {
granteeUserIdControl?.clearValidators();
granteeUserIdControl?.setValue(null);
permissionsControl?.setValue(this.data.access.permissions[0]);
permissionsControl?.setValue(
access?.permissions[0] ?? AccessPermission.READ_RESTRICTED
);
}
granteeUserIdControl?.updateValueAndValidity();
@ -109,11 +116,11 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
});
}
public onCancel() {
protected onCancel() {
this.dialogRef.close();
}
public async onSubmit() {
protected async onSubmit() {
if (this.mode === 'create') {
await this.createAccess();
} else {
@ -158,10 +165,16 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
}
private async updateAccess() {
const accessId = this.data.access?.id;
if (!accessId) {
return;
}
const access: UpdateAccessDto = {
alias: this.accessForm.get('alias')?.value,
granteeUserId: this.accessForm.get('granteeUserId')?.value,
id: this.data.access.id,
id: accessId,
permissions: [this.accessForm.get('permissions')?.value]
};

2
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/interfaces/interfaces.ts

@ -1,5 +1,5 @@
import { Access } from '@ghostfolio/common/interfaces';
export interface CreateOrUpdateAccessDialogParams {
access: Access;
access?: Access;
}

93
apps/client/src/app/components/user-account-access/user-account-access.component.ts

@ -12,12 +12,19 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
inject,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
import {
FormControl,
FormGroup,
ReactiveFormsModule,
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
@ -53,30 +60,35 @@ import { CreateOrUpdateAccessDialogParams } from './create-or-update-access-dial
templateUrl: './user-account-access.html'
})
export class GfUserAccountAccessComponent implements OnInit {
public accessesGet: Access[];
public accessesGive: Access[];
public deviceType: string;
public hasPermissionToCreateAccess: boolean;
public hasPermissionToDeleteAccess: boolean;
public hasPermissionToUpdateOwnAccessToken: boolean;
public isAccessTokenHidden = true;
public updateOwnAccessTokenForm = this.formBuilder.group({
accessToken: ['', Validators.required]
protected accessesGet: Access[];
protected accessesGive: Access[];
protected hasPermissionToCreateAccess: boolean;
protected hasPermissionToDeleteAccess: boolean;
protected hasPermissionToUpdateOwnAccessToken: boolean;
protected isAccessTokenHidden = true;
protected readonly updateOwnAccessTokenForm = new FormGroup({
accessToken: new FormControl<string>('', {
nonNullable: true,
validators: [Validators.required]
})
});
public user: User;
protected user: User;
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private destroyRef: DestroyRef,
private deviceDetectorService: DeviceDetectorService,
private dialog: MatDialog,
private formBuilder: FormBuilder,
private notificationService: NotificationService,
private route: ActivatedRoute,
private router: Router,
private userService: UserService
) {
private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
);
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog);
private readonly notificationService = inject(NotificationService);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly userService = inject(UserService);
public constructor() {
const { globalPermissions } = this.dataService.fetchInfo();
this.hasPermissionToDeleteAccess = hasPermission(
@ -123,12 +135,10 @@ export class GfUserAccountAccessComponent implements OnInit {
}
public ngOnInit() {
this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
this.update();
}
public onDeleteAccess(aId: string) {
protected onDeleteAccess(aId: string) {
this.dataService
.deleteAccess(aId)
.pipe(takeUntilDestroyed(this.destroyRef))
@ -139,12 +149,13 @@ export class GfUserAccountAccessComponent implements OnInit {
});
}
public onGenerateAccessToken() {
protected onGenerateAccessToken() {
this.notificationService.confirm({
confirmFn: () => {
this.dataService
.updateOwnAccessToken({
accessToken: this.updateOwnAccessTokenForm.get('accessToken').value
accessToken:
this.updateOwnAccessTokenForm.controls.accessToken.value
})
.pipe(
catchError(() => {
@ -173,7 +184,7 @@ export class GfUserAccountAccessComponent implements OnInit {
});
}
public onUpdateAccess(aId: string) {
protected onUpdateAccess(aId: string) {
this.router.navigate([], {
queryParams: { accessId: aId, editDialog: true }
});
@ -184,17 +195,9 @@ export class GfUserAccountAccessComponent implements OnInit {
GfCreateOrUpdateAccessDialogComponent,
CreateOrUpdateAccessDialogParams
>(GfCreateOrUpdateAccessDialogComponent, {
data: {
access: {
alias: '',
grantee: null,
id: null,
permissions: ['READ_RESTRICTED'],
type: 'PRIVATE'
}
},
height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
data: {} satisfies CreateOrUpdateAccessDialogParams,
height: this.deviceType() === 'mobile' ? '98vh' : undefined,
width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
});
dialogRef.afterClosed().subscribe((access: CreateAccessDto | null) => {
@ -222,14 +225,14 @@ export class GfUserAccountAccessComponent implements OnInit {
data: {
access: {
alias: access.alias,
grantee: access.grantee === 'Public' ? null : access.grantee,
grantee: access.grantee === 'Public' ? undefined : access.grantee,
id: access.id,
permissions: access.permissions,
type: access.type
}
},
height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
} satisfies CreateOrUpdateAccessDialogParams,
height: this.deviceType() === 'mobile' ? '98vh' : undefined,
width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
});
dialogRef.afterClosed().subscribe((result) => {
@ -244,9 +247,9 @@ export class GfUserAccountAccessComponent implements OnInit {
private update() {
this.accessesGet = this.user.access.map(({ alias, id, permissions }) => {
return {
alias,
id,
permissions,
alias: alias ?? '',
grantee: $localize`Me`,
type: 'PRIVATE'
};

Loading…
Cancel
Save