From 2de0e75cb879e0c8746c0e7dd9a0e92592d7d2f1 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Mon, 25 Oct 2021 20:46:36 +0200 Subject: [PATCH] Feature/add user interface for granting and revoking public access (#439) * Add user interface for granting and revoking public access * Update changelog --- CHANGELOG.md | 6 +- apps/api/src/app/access/access.controller.ts | 66 ++++++++++++++- apps/api/src/app/access/access.service.ts | 16 +++- apps/api/src/app/access/create-access.dto.ts | 1 + .../src/app/portfolio/portfolio.controller.ts | 6 +- .../access-table/access-table.component.html | 50 +++++++++--- .../access-table/access-table.component.scss | 8 ++ .../access-table/access-table.component.ts | 25 +++++- .../access-table/access-table.module.ts | 4 +- .../pages/account/account-page.component.ts | 81 +++++++++++++++++++ .../src/app/pages/account/account-page.html | 20 ++++- .../app/pages/account/account-page.module.ts | 2 + .../src/app/pages/account/account-page.scss | 20 +++++ ...reate-or-update-access-dialog.component.ts | 37 +++++++++ .../create-or-update-access-dialog.html | 25 ++++++ .../create-or-update-access-dialog.module.ts | 25 ++++++ .../create-or-update-access-dialog.scss | 7 ++ .../interfaces/interfaces.ts | 5 ++ .../pages/accounts/accounts-page.component.ts | 2 +- .../create-or-update-account-dialog.html | 4 +- .../src/app/pages/public/public-page.html | 4 +- apps/client/src/app/services/data.service.ts | 10 ++- libs/common/src/lib/permissions.ts | 6 ++ 23 files changed, 401 insertions(+), 29 deletions(-) create mode 100644 apps/api/src/app/access/create-access.dto.ts create mode 100644 apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts create mode 100644 apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html create mode 100644 apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.module.ts create mode 100644 apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.scss create mode 100644 apps/client/src/app/pages/account/create-or-update-access-dialog/interfaces/interfaces.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b752d0..537cf6f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added the user interface for granting and revoking public access to share the portfolio + ### Changed - Moved the data enhancer calls from the data provider (`get()`) to the data gathering service to reduce traffic to 3rd party data providers @@ -26,7 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added a public page to share your portfolio +- Added a public page to share the portfolio ### Changed diff --git a/apps/api/src/app/access/access.controller.ts b/apps/api/src/app/access/access.controller.ts index b9a1f9b2..f7e4dff4 100644 --- a/apps/api/src/app/access/access.controller.ts +++ b/apps/api/src/app/access/access.controller.ts @@ -1,10 +1,29 @@ import { Access } from '@ghostfolio/common/interfaces'; +import { + getPermissions, + hasPermission, + permissions +} from '@ghostfolio/common/permissions'; import type { RequestWithUser } from '@ghostfolio/common/types'; -import { Controller, Get, Inject, UseGuards } from '@nestjs/common'; +import { + Body, + Controller, + Delete, + Get, + HttpException, + Inject, + Param, + Post, + UseGuards +} from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; +import { Access as AccessModel } from '@prisma/client'; +import { StatusCodes, getReasonPhrase } from 'http-status-codes'; +import { AccessModule } from './access.module'; import { AccessService } from './access.service'; +import { CreateAccessDto } from './create-access.dto'; @Controller('access') export class AccessController { @@ -39,4 +58,49 @@ export class AccessController { }; }); } + + @Post() + @UseGuards(AuthGuard('jwt')) + public async createAccess( + @Body() data: CreateAccessDto + ): Promise { + if ( + !hasPermission( + getPermissions(this.request.user.role), + permissions.createAccess + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + return this.accessService.createAccess({ + User: { connect: { id: this.request.user.id } } + }); + } + + @Delete(':id') + @UseGuards(AuthGuard('jwt')) + public async deleteAccess(@Param('id') id: string): Promise { + if ( + !hasPermission( + getPermissions(this.request.user.role), + permissions.deleteAccess + ) + ) { + throw new HttpException( + getReasonPhrase(StatusCodes.FORBIDDEN), + StatusCodes.FORBIDDEN + ); + } + + return this.accessService.deleteAccess({ + id_userId: { + id, + userId: this.request.user.id + } + }); + } } diff --git a/apps/api/src/app/access/access.service.ts b/apps/api/src/app/access/access.service.ts index c09c9d28..f2f98eb2 100644 --- a/apps/api/src/app/access/access.service.ts +++ b/apps/api/src/app/access/access.service.ts @@ -1,7 +1,7 @@ import { PrismaService } from '@ghostfolio/api/services/prisma.service'; import { AccessWithGranteeUser } from '@ghostfolio/common/types'; import { Injectable } from '@nestjs/common'; -import { Prisma } from '@prisma/client'; +import { Access, Prisma } from '@prisma/client'; @Injectable() export class AccessService { @@ -37,4 +37,18 @@ export class AccessService { where }); } + + public async createAccess(data: Prisma.AccessCreateInput): Promise { + return this.prismaService.access.create({ + data + }); + } + + public async deleteAccess( + where: Prisma.AccessWhereUniqueInput + ): Promise { + return this.prismaService.access.delete({ + where + }); + } } diff --git a/apps/api/src/app/access/create-access.dto.ts b/apps/api/src/app/access/create-access.dto.ts new file mode 100644 index 00000000..672e195c --- /dev/null +++ b/apps/api/src/app/access/create-access.dto.ts @@ -0,0 +1 @@ +export class CreateAccessDto {} diff --git a/apps/api/src/app/portfolio/portfolio.controller.ts b/apps/api/src/app/portfolio/portfolio.controller.ts index b07579cf..777093f3 100644 --- a/apps/api/src/app/portfolio/portfolio.controller.ts +++ b/apps/api/src/app/portfolio/portfolio.controller.ts @@ -272,7 +272,7 @@ export class PortfolioController { return res.json({ accounts: {}, holdings: {} }); } - const { hasErrors, holdings } = await this.portfolioService.getDetails( + const { holdings } = await this.portfolioService.getDetails( access.userId, access.userId ); @@ -281,10 +281,6 @@ export class PortfolioController { holdings: {} }; - if (hasErrors || hasNotDefinedValuesInObject(holdings)) { - res.status(StatusCodes.ACCEPTED); - } - const totalValue = Object.values(holdings) .filter((holding) => { return holding.assetClass === 'EQUITY'; diff --git a/apps/client/src/app/components/access-table/access-table.component.html b/apps/client/src/app/components/access-table/access-table.component.html index 468267ac..be184b0e 100644 --- a/apps/client/src/app/components/access-table/access-table.component.html +++ b/apps/client/src/app/components/access-table/access-table.component.html @@ -1,24 +1,52 @@ - + + + - + + + + + + + + + + + + + diff --git a/apps/client/src/app/components/access-table/access-table.component.scss b/apps/client/src/app/components/access-table/access-table.component.scss index b97d286c..ad5f401f 100644 --- a/apps/client/src/app/components/access-table/access-table.component.scss +++ b/apps/client/src/app/components/access-table/access-table.component.scss @@ -2,4 +2,12 @@ :host { display: block; + + a { + color: rgba(var(--palette-primary-500), 1); + + &:hover { + color: rgba(var(--palette-primary-300), 1); + } + } } diff --git a/apps/client/src/app/components/access-table/access-table.component.ts b/apps/client/src/app/components/access-table/access-table.component.ts index 4136197b..210995c5 100644 --- a/apps/client/src/app/components/access-table/access-table.component.ts +++ b/apps/client/src/app/components/access-table/access-table.component.ts @@ -1,9 +1,11 @@ import { ChangeDetectionStrategy, Component, + EventEmitter, Input, OnChanges, - OnInit + OnInit, + Output } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { Access } from '@ghostfolio/common/interfaces'; @@ -16,18 +18,37 @@ import { Access } from '@ghostfolio/common/interfaces'; }) export class AccessTableComponent implements OnChanges, OnInit { @Input() accesses: Access[]; + @Input() showActions: boolean; + + @Output() accessDeleted = new EventEmitter(); public baseUrl = window.location.origin; public dataSource: MatTableDataSource; - public displayedColumns = ['granteeAlias', 'type']; + public displayedColumns = []; public constructor() {} public ngOnInit() {} public ngOnChanges() { + this.displayedColumns = ['granteeAlias', 'type', 'details']; + + if (this.showActions) { + this.displayedColumns.push('actions'); + } + if (this.accesses) { this.dataSource = new MatTableDataSource(this.accesses); } } + + public onDeleteAccess(aId: string) { + const confirmation = confirm( + 'Do you really want to revoke this granted access?' + ); + + if (confirmation) { + this.accessDeleted.emit(aId); + } + } } diff --git a/apps/client/src/app/components/access-table/access-table.module.ts b/apps/client/src/app/components/access-table/access-table.module.ts index 754795c5..a1c7d627 100644 --- a/apps/client/src/app/components/access-table/access-table.module.ts +++ b/apps/client/src/app/components/access-table/access-table.module.ts @@ -1,5 +1,7 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatMenuModule } from '@angular/material/menu'; import { MatTableModule } from '@angular/material/table'; import { AccessTableComponent } from './access-table.component'; @@ -7,7 +9,7 @@ import { AccessTableComponent } from './access-table.component'; @NgModule({ declarations: [AccessTableComponent], exports: [AccessTableComponent], - imports: [CommonModule, MatTableModule], + imports: [CommonModule, MatButtonModule, MatMenuModule, MatTableModule], providers: [], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) diff --git a/apps/client/src/app/pages/account/account-page.component.ts b/apps/client/src/app/pages/account/account-page.component.ts index 3d941250..244c57cc 100644 --- a/apps/client/src/app/pages/account/account-page.component.ts +++ b/apps/client/src/app/pages/account/account-page.component.ts @@ -5,20 +5,26 @@ import { OnInit, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; import { MatSlideToggle, MatSlideToggleChange } from '@angular/material/slide-toggle'; +import { ActivatedRoute, Router } from '@angular/router'; +import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { DataService } from '@ghostfolio/client/services/data.service'; import { UserService } from '@ghostfolio/client/services/user/user.service'; import { WebAuthnService } from '@ghostfolio/client/services/web-authn.service'; import { DEFAULT_DATE_FORMAT, baseCurrency } from '@ghostfolio/common/config'; import { Access, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; +import { DeviceDetectorService } from 'ngx-device-detector'; import { StripeService } from 'ngx-stripe'; import { EMPTY, Subject } from 'rxjs'; import { catchError, switchMap, takeUntil } from 'rxjs/operators'; +import { CreateOrUpdateAccessDialog } from './create-or-update-access-dialog/create-or-update-access-dialog.component'; + @Component({ host: { class: 'mb-5' }, selector: 'gf-account-page', @@ -35,7 +41,10 @@ export class AccountPageComponent implements OnDestroy, OnInit { public couponId: string; public currencies: string[] = []; public defaultDateFormat = DEFAULT_DATE_FORMAT; + public deviceType: string; public hasPermissionForSubscription: boolean; + public hasPermissionToCreateAccess: boolean; + public hasPermissionToDeleteAccess: boolean; public hasPermissionToUpdateViewMode: boolean; public hasPermissionToUpdateUserSettings: boolean; public price: number; @@ -50,6 +59,10 @@ export class AccountPageComponent implements OnDestroy, OnInit { public constructor( private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, + private deviceService: DeviceDetectorService, + private dialog: MatDialog, + private route: ActivatedRoute, + private router: Router, private stripeService: StripeService, private userService: UserService, public webAuthnService: WebAuthnService @@ -65,6 +78,11 @@ export class AccountPageComponent implements OnDestroy, OnInit { permissions.enableSubscription ); + this.hasPermissionToDeleteAccess = hasPermission( + globalPermissions, + permissions.deleteAccess + ); + this.price = subscriptions?.[0]?.price; this.priceId = subscriptions?.[0]?.priceId; @@ -74,6 +92,16 @@ export class AccountPageComponent implements OnDestroy, OnInit { if (state?.user) { this.user = state.user; + this.hasPermissionToCreateAccess = hasPermission( + this.user.permissions, + permissions.createAccess + ); + + this.hasPermissionToDeleteAccess = hasPermission( + this.user.permissions, + permissions.deleteAccess + ); + this.hasPermissionToUpdateUserSettings = hasPermission( this.user.permissions, permissions.updateUserSettings @@ -87,12 +115,22 @@ export class AccountPageComponent implements OnDestroy, OnInit { this.changeDetectorRef.markForCheck(); } }); + + this.route.queryParams + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((params) => { + if (params['createDialog']) { + this.openCreateAccessDialog(); + } + }); } /** * Initializes the controller */ public ngOnInit() { + this.deviceType = this.deviceService.getDeviceInfo().deviceType; + this.update(); } @@ -136,6 +174,17 @@ export class AccountPageComponent implements OnDestroy, OnInit { }); } + public onDeleteAccess(aId: string) { + this.dataService + .deleteAccess(aId) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe({ + next: () => { + this.update(); + } + }); + } + public onRestrictedViewChange(aEvent: MatSlideToggleChange) { this.dataService .putUserSetting({ isRestrictedView: aEvent.checked }) @@ -175,6 +224,38 @@ export class AccountPageComponent implements OnDestroy, OnInit { this.unsubscribeSubject.complete(); } + private openCreateAccessDialog(): void { + const dialogRef = this.dialog.open(CreateOrUpdateAccessDialog, { + data: { + access: { + type: 'PUBLIC' + } + }, + height: this.deviceType === 'mobile' ? '97.5vh' : '80vh', + width: this.deviceType === 'mobile' ? '100vw' : '50rem' + }); + + dialogRef + .afterClosed() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((data: any) => { + const access: CreateAccessDto = data?.access; + + if (access) { + this.dataService + .postAccess({}) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe({ + next: () => { + this.update(); + } + }); + } + + this.router.navigate(['.'], { relativeTo: this.route }); + }); + } + private deregisterDevice() { this.webAuthnService .deregister() diff --git a/apps/client/src/app/pages/account/account-page.html b/apps/client/src/app/pages/account/account-page.html index 87e947a2..9ffd2159 100644 --- a/apps/client/src/app/pages/account/account-page.html +++ b/apps/client/src/app/pages/account/account-page.html @@ -132,10 +132,26 @@ -
+

Granted Access

- +
+ +
+ + + +
diff --git a/apps/client/src/app/pages/account/account-page.module.ts b/apps/client/src/app/pages/account/account-page.module.ts index 8e7cddec..5b3d8ce3 100644 --- a/apps/client/src/app/pages/account/account-page.module.ts +++ b/apps/client/src/app/pages/account/account-page.module.ts @@ -12,6 +12,7 @@ import { GfPortfolioAccessTableModule } from '@ghostfolio/client/components/acce import { AccountPageRoutingModule } from './account-page-routing.module'; import { AccountPageComponent } from './account-page.component'; +import { GfCreateOrUpdateAccessDialogModule } from './create-or-update-access-dialog/create-or-update-access-dialog.module'; @NgModule({ declarations: [AccountPageComponent], @@ -20,6 +21,7 @@ import { AccountPageComponent } from './account-page.component'; AccountPageRoutingModule, CommonModule, FormsModule, + GfCreateOrUpdateAccessDialogModule, GfPortfolioAccessTableModule, MatButtonModule, MatCardModule, diff --git a/apps/client/src/app/pages/account/account-page.scss b/apps/client/src/app/pages/account/account-page.scss index 37599724..4a798522 100644 --- a/apps/client/src/app/pages/account/account-page.scss +++ b/apps/client/src/app/pages/account/account-page.scss @@ -2,6 +2,26 @@ color: rgb(var(--dark-primary-text)); display: block; + gf-access-table { + overflow-x: auto; + + table { + min-width: 100%; + + .mat-row, + .mat-header-row { + width: 100%; + } + } + } + + .fab-container { + position: fixed; + right: 2rem; + bottom: 2rem; + z-index: 999; + } + .hint-text { font-size: 90%; line-height: 1.2; diff --git a/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts new file mode 100644 index 00000000..1235caab --- /dev/null +++ b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.component.ts @@ -0,0 +1,37 @@ +import { + ChangeDetectionStrategy, + Component, + Inject, + OnDestroy +} from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { Subject } from 'rxjs'; + +import { CreateOrUpdateAccessDialogParams } from './interfaces/interfaces'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'h-100' }, + selector: 'gf-create-or-update-access-dialog', + styleUrls: ['./create-or-update-access-dialog.scss'], + templateUrl: 'create-or-update-access-dialog.html' +}) +export class CreateOrUpdateAccessDialog implements OnDestroy { + private unsubscribeSubject = new Subject(); + + public constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: CreateOrUpdateAccessDialogParams + ) {} + + ngOnInit() {} + + public onCancel(): void { + this.dialogRef.close(); + } + + public ngOnDestroy() { + this.unsubscribeSubject.next(); + this.unsubscribeSubject.complete(); + } +} diff --git a/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html new file mode 100644 index 00000000..49273650 --- /dev/null +++ b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.html @@ -0,0 +1,25 @@ +
+

Grant access

+
+
+ + Type + + Public + + +
+
+
+ + +
+ diff --git a/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.module.ts b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.module.ts new file mode 100644 index 00000000..367c489e --- /dev/null +++ b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.module.ts @@ -0,0 +1,25 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatSelectModule } from '@angular/material/select'; + +import { CreateOrUpdateAccessDialog } from './create-or-update-access-dialog.component'; + +@NgModule({ + declarations: [CreateOrUpdateAccessDialog], + exports: [], + imports: [ + CommonModule, + FormsModule, + MatButtonModule, + MatDialogModule, + MatFormFieldModule, + MatSelectModule, + ReactiveFormsModule + ], + providers: [] +}) +export class GfCreateOrUpdateAccessDialogModule {} diff --git a/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.scss b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.scss new file mode 100644 index 00000000..ce1c7d59 --- /dev/null +++ b/apps/client/src/app/pages/account/create-or-update-access-dialog/create-or-update-access-dialog.scss @@ -0,0 +1,7 @@ +:host { + display: block; + + .mat-dialog-content { + max-height: unset; + } +} diff --git a/apps/client/src/app/pages/account/create-or-update-access-dialog/interfaces/interfaces.ts b/apps/client/src/app/pages/account/create-or-update-access-dialog/interfaces/interfaces.ts new file mode 100644 index 00000000..b7850fb3 --- /dev/null +++ b/apps/client/src/app/pages/account/create-or-update-access-dialog/interfaces/interfaces.ts @@ -0,0 +1,5 @@ +import { Access } from '@ghostfolio/common/interfaces'; + +export interface CreateOrUpdateAccessDialogParams { + access: Access; +} diff --git a/apps/client/src/app/pages/accounts/accounts-page.component.ts b/apps/client/src/app/pages/accounts/accounts-page.component.ts index 711a8f85..cf9324c1 100644 --- a/apps/client/src/app/pages/accounts/accounts-page.component.ts +++ b/apps/client/src/app/pages/accounts/accounts-page.component.ts @@ -45,7 +45,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit { private router: Router, private userService: UserService ) { - this.routeQueryParams = route.queryParams + this.route.queryParams .pipe(takeUntil(this.unsubscribeSubject)) .subscribe((params) => { if (params['createDialog']) { diff --git a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html index 9e8d8f5a..9c442764 100644 --- a/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html +++ b/apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html @@ -12,8 +12,8 @@ Type - Cash - Securities + Cash + Securities diff --git a/apps/client/src/app/pages/public/public-page.html b/apps/client/src/app/pages/public/public-page.html index 9bbd7403..7cbca7a8 100644 --- a/apps/client/src/app/pages/public/public-page.html +++ b/apps/client/src/app/pages/public/public-page.html @@ -1,7 +1,9 @@
-

Portfolio

+

+ Hello, someone has shared a Portfolio with you! +

diff --git a/apps/client/src/app/services/data.service.ts b/apps/client/src/app/services/data.service.ts index e1c29710..43ea50d9 100644 --- a/apps/client/src/app/services/data.service.ts +++ b/apps/client/src/app/services/data.service.ts @@ -1,8 +1,8 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto'; import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto'; import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto'; -import { ImportDataDto } from '@ghostfolio/api/app/import/import-data.dto'; import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { @@ -69,6 +69,10 @@ export class DataService { return this.http.get('/api/admin'); } + public deleteAccess(aId: string) { + return this.http.delete(`/api/access/${aId}`); + } + public deleteAccount(aId: string) { return this.http.delete(`/api/account/${aId}`); } @@ -197,6 +201,10 @@ export class DataService { return this.http.get(`/api/auth/anonymous/${accessToken}`); } + public postAccess(aAccess: CreateAccessDto) { + return this.http.post(`/api/access`, aAccess); + } + public postAccount(aAccount: CreateAccountDto) { return this.http.post(`/api/account`, aAccount); } diff --git a/libs/common/src/lib/permissions.ts b/libs/common/src/lib/permissions.ts index f0110312..569fa25d 100644 --- a/libs/common/src/lib/permissions.ts +++ b/libs/common/src/lib/permissions.ts @@ -7,9 +7,11 @@ export function isApiTokenAuthorized(aApiToken: string) { export const permissions = { accessAdminControl: 'accessAdminControl', accessFearAndGreedIndex: 'accessFearAndGreedIndex', + createAccess: 'createAccess', createAccount: 'createAccount', createOrder: 'createOrder', createUserAccount: 'createUserAccount', + deleteAccess: 'deleteAccess', deleteAccount: 'deleteAcccount', deleteAuthDevice: 'deleteAuthDevice', deleteOrder: 'deleteOrder', @@ -38,8 +40,10 @@ export function getPermissions(aRole: Role): string[] { case 'ADMIN': return [ permissions.accessAdminControl, + permissions.createAccess, permissions.createAccount, permissions.createOrder, + permissions.deleteAccess, permissions.deleteAccount, permissions.deleteAuthDevice, permissions.deleteOrder, @@ -56,8 +60,10 @@ export function getPermissions(aRole: Role): string[] { case 'USER': return [ + permissions.createAccess, permissions.createAccount, permissions.createOrder, + permissions.deleteAccess, permissions.deleteAccount, permissions.deleteAuthDevice, permissions.deleteOrder,
UserGrantee {{ element.granteeAlias }} - Type - - - {{ baseUrl }}/p/{{ element.id }} - - + + Restricted View - Details + + + {{ baseUrl }}/p/{{ element.id }} + + + + + + +