Browse Source

Add write access

pull/7676/head
Thomas Kaul 1 week ago
parent
commit
2db36f4acc
  1. 10
      apps/client/src/app/components/access-table/access-table.component.ts
  2. 59
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  3. 24
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html
  4. 2
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/interfaces/interfaces.ts

10
apps/client/src/app/components/access-table/access-table.component.ts

@ -1,7 +1,11 @@
import { ConfirmationDialogType } from '@ghostfolio/common/enums'; import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { Access, User } from '@ghostfolio/common/interfaces'; import { Access, User } from '@ghostfolio/common/interfaces';
import { publicRoutes } from '@ghostfolio/common/routes/routes'; import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { hasScope, scopes } from '@ghostfolio/common/scopes'; import {
SCOPES_OF_WRITE_ACCESS,
hasScope,
scopes
} from '@ghostfolio/common/scopes';
import { NotificationService } from '@ghostfolio/ui/notifications'; import { NotificationService } from '@ghostfolio/ui/notifications';
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'; import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard';
@ -106,7 +110,9 @@ export class GfAccessTableComponent {
} }
protected hasScopesToWrite({ scopes: scopesOfAccess }: Access) { protected hasScopesToWrite({ scopes: scopesOfAccess }: Access) {
return hasScope(scopesOfAccess, scopes.activityCreate); return SCOPES_OF_WRITE_ACCESS.some((scope) => {
return hasScope(scopesOfAccess, scope);
});
} }
protected onCopyUrlToClipboard(aId: string) { protected onCopyUrlToClipboard(aId: string) {

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

@ -5,6 +5,7 @@ import {
SCOPES_OF_READ_ACCESS, SCOPES_OF_READ_ACCESS,
SCOPES_OF_READ_RESTRICTED_ACCESS, SCOPES_OF_READ_RESTRICTED_ACCESS,
SCOPES_OF_WRITE_ACCESS, SCOPES_OF_WRITE_ACCESS,
Scope,
hasScope, hasScope,
scopes scopes
} from '@ghostfolio/common/scopes'; } from '@ghostfolio/common/scopes';
@ -47,11 +48,13 @@ import {
import { MatFormFieldModule } from '@angular/material/form-field'; import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import { EMPTY, catchError } from 'rxjs'; import { EMPTY, catchError } from 'rxjs';
import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces'; import {
AccessLevel,
CreateOrUpdateAccessDialogParams
} from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -64,7 +67,6 @@ import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces';
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
MatSelectModule, MatSelectModule,
MatSlideToggleModule,
ReactiveFormsModule ReactiveFormsModule
], ],
selector: 'gf-create-or-update-access-dialog', selector: 'gf-create-or-update-access-dialog',
@ -113,17 +115,13 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
const isPublic = access?.type === 'PUBLIC'; const isPublic = access?.type === 'PUBLIC';
this.accessForm = this.formBuilder.group({ this.accessForm = this.formBuilder.group({
accessLevel: this.getAccessLevel(access?.scopes),
alias: [access?.alias ?? ''], alias: [access?.alias ?? ''],
filters: [null], filters: [null],
granteeUserId: [ granteeUserId: [
access?.grantee ?? null, access?.grantee ?? null,
isPublic ? null : Validators.required isPublic ? null : Validators.required
], ],
hasScopeToReadValues: hasScope(
access?.scopes,
scopes.portfolioReadValues
),
hasScopesToWrite: hasScope(access?.scopes, scopes.activityCreate),
type: [ type: [
{ disabled: this.mode === 'update', value: access?.type ?? 'PRIVATE' }, { disabled: this.mode === 'update', value: access?.type ?? 'PRIVATE' },
Validators.required Validators.required
@ -149,12 +147,6 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
.subscribe((accessType) => { .subscribe((accessType) => {
const granteeUserIdControl = this.accessForm.get('granteeUserId'); const granteeUserIdControl = this.accessForm.get('granteeUserId');
const hasScopeToReadValuesControl = this.accessForm.get(
'hasScopeToReadValues'
);
const hasScopesToWriteControl = this.accessForm.get('hasScopesToWrite');
if (accessType === 'PRIVATE') { if (accessType === 'PRIVATE') {
granteeUserIdControl?.setValidators(Validators.required); granteeUserIdControl?.setValidators(Validators.required);
this.accessForm.get('filters')?.setValue(null); this.accessForm.get('filters')?.setValue(null);
@ -164,8 +156,7 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
// A public access never exposes the monetary values and never // A public access never exposes the monetary values and never
// changes data // changes data
hasScopeToReadValuesControl?.setValue(false); this.accessForm.get('accessLevel')?.setValue('RESTRICTED_VIEW');
hasScopesToWriteControl?.setValue(false);
} }
granteeUserIdControl?.updateValueAndValidity(); granteeUserIdControl?.updateValueAndValidity();
@ -194,19 +185,15 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
); );
} }
/** private buildScopes(): Scope[] {
* The write access is granted as one unit, hence the dialog offers a single switch (this.accessForm.get('accessLevel')?.value as AccessLevel) {
* control for all write scopes case 'CHANGE':
*/ return [...SCOPES_OF_READ_ACCESS, ...SCOPES_OF_WRITE_ACCESS];
private buildScopes() { case 'VIEW':
return [ return [...SCOPES_OF_READ_ACCESS];
...(this.accessForm.get('hasScopeToReadValues')?.value default:
? SCOPES_OF_READ_ACCESS return [...SCOPES_OF_READ_RESTRICTED_ACCESS];
: SCOPES_OF_READ_RESTRICTED_ACCESS), }
...(this.accessForm.get('hasScopesToWrite')?.value
? SCOPES_OF_WRITE_ACCESS
: [])
];
} }
private async createAccess() { private async createAccess() {
@ -248,6 +235,20 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
} }
} }
private getAccessLevel(scopesOfAccess: string[] | undefined): AccessLevel {
if (
SCOPES_OF_WRITE_ACCESS.some((scope) => {
return hasScope(scopesOfAccess, scope);
})
) {
return 'CHANGE';
}
return hasScope(scopesOfAccess, scopes.portfolioReadValues)
? 'VIEW'
: 'RESTRICTED_VIEW';
}
private loadHoldings() { private loadHoldings() {
this.dataService this.dataService
.fetchPortfolioHoldings() .fetchPortfolioHoldings()

24
apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html

@ -36,31 +36,15 @@
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Permission</mat-label> <mat-label i18n>Permission</mat-label>
<mat-select formControlName="hasScopeToReadValues"> <mat-select formControlName="accessLevel">
<mat-option i18n [value]="false">Restricted view</mat-option> <mat-option i18n value="RESTRICTED_VIEW">Restricted view</mat-option>
@if (accessForm.get('type')?.value === 'PRIVATE') { @if (accessForm.get('type')?.value === 'PRIVATE') {
<mat-option i18n [value]="true">View</mat-option> <mat-option i18n value="VIEW">View</mat-option>
<mat-option i18n value="CHANGE">View and change</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
@if (accessForm.get('type')?.value === 'PRIVATE') {
<div class="align-items-center d-flex mb-3">
<div class="w-50">
<div i18n>Changes</div>
<div class="hint-text text-muted" i18n>
Allow to add, to update and to delete data of your portfolio
</div>
</div>
<div class="pl-1 w-50">
<mat-slide-toggle
color="primary"
formControlName="hasScopesToWrite"
hideIcon="true"
/>
</div>
</div>
}
@if (accessForm.get('type')?.value === 'PRIVATE') { @if (accessForm.get('type')?.value === 'PRIVATE') {
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">

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

@ -3,3 +3,5 @@ import { Access } from '@ghostfolio/common/interfaces';
export interface CreateOrUpdateAccessDialogParams { export interface CreateOrUpdateAccessDialogParams {
access?: Access; access?: Access;
} }
export type AccessLevel = 'CHANGE' | 'RESTRICTED_VIEW' | 'VIEW';

Loading…
Cancel
Save