Browse Source

Add business logic

pull/2870/head
Thomas Kaul 2 years ago
parent
commit
fdb4f811e8
  1. 34
      apps/api/src/app/portfolio/portfolio.controller.ts
  2. 25
      apps/api/src/app/user/user.service.ts
  3. 18
      apps/api/src/interceptors/redact-values-in-response.interceptor.ts
  4. 2
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.html
  5. 3
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/interfaces/interfaces.ts
  6. 4
      apps/client/src/app/components/user-account-access/user-account-access.component.ts
  7. 3
      libs/common/src/lib/types/user-with-settings.type.ts

34
apps/api/src/app/portfolio/portfolio.controller.ts

@ -74,6 +74,11 @@ export class PortfolioController {
): Promise<PortfolioDetails & { hasError: boolean }> { ): Promise<PortfolioDetails & { hasError: boolean }> {
let hasDetails = true; let hasDetails = true;
let hasError = false; let hasError = false;
const hasReadRestrictedAccessPermission =
this.userService.hasReadRestrictedAccessPermission({
impersonationId,
user: this.request.user
});
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
hasDetails = this.request.user.subscription.type === 'Premium'; hasDetails = this.request.user.subscription.type === 'Premium';
@ -108,7 +113,7 @@ export class PortfolioController {
let portfolioSummary = summary; let portfolioSummary = summary;
if ( if (
impersonationId || hasReadRestrictedAccessPermission ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
) { ) {
const totalInvestment = Object.values(holdings) const totalInvestment = Object.values(holdings)
@ -148,7 +153,7 @@ export class PortfolioController {
if ( if (
hasDetails === false || hasDetails === false ||
impersonationId || hasReadRestrictedAccessPermission ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
) { ) {
portfolioSummary = nullifyValuesInObject(summary, [ portfolioSummary = nullifyValuesInObject(summary, [
@ -164,6 +169,7 @@ export class PortfolioController {
'excludedAccountsAndActivities', 'excludedAccountsAndActivities',
'fees', 'fees',
'fireWealth', 'fireWealth',
'interest',
'items', 'items',
'liabilities', 'liabilities',
'netWorth', 'netWorth',
@ -216,6 +222,12 @@ export class PortfolioController {
@Query('range') dateRange: DateRange = 'max', @Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string @Query('tags') filterByTags?: string
): Promise<PortfolioDividends> { ): Promise<PortfolioDividends> {
const hasReadRestrictedAccessPermission =
this.userService.hasReadRestrictedAccessPermission({
impersonationId,
user: this.request.user
});
const filters = this.apiService.buildFiltersFromQueryParams({ const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts, filterByAccounts,
filterByAssetClasses, filterByAssetClasses,
@ -230,7 +242,7 @@ export class PortfolioController {
}); });
if ( if (
impersonationId || hasReadRestrictedAccessPermission ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
) { ) {
const maxDividend = dividends.reduce( const maxDividend = dividends.reduce(
@ -266,6 +278,12 @@ export class PortfolioController {
@Query('range') dateRange: DateRange = 'max', @Query('range') dateRange: DateRange = 'max',
@Query('tags') filterByTags?: string @Query('tags') filterByTags?: string
): Promise<PortfolioInvestments> { ): Promise<PortfolioInvestments> {
const hasReadRestrictedAccessPermission =
this.userService.hasReadRestrictedAccessPermission({
impersonationId,
user: this.request.user
});
const filters = this.apiService.buildFiltersFromQueryParams({ const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts, filterByAccounts,
filterByAssetClasses, filterByAssetClasses,
@ -281,7 +299,7 @@ export class PortfolioController {
}); });
if ( if (
impersonationId || hasReadRestrictedAccessPermission ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
) { ) {
const maxInvestment = investments.reduce( const maxInvestment = investments.reduce(
@ -329,6 +347,12 @@ export class PortfolioController {
@Query('tags') filterByTags?: string, @Query('tags') filterByTags?: string,
@Query('withExcludedAccounts') withExcludedAccounts = false @Query('withExcludedAccounts') withExcludedAccounts = false
): Promise<PortfolioPerformanceResponse> { ): Promise<PortfolioPerformanceResponse> {
const hasReadRestrictedAccessPermission =
this.userService.hasReadRestrictedAccessPermission({
impersonationId,
user: this.request.user
});
const filters = this.apiService.buildFiltersFromQueryParams({ const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts, filterByAccounts,
filterByAssetClasses, filterByAssetClasses,
@ -344,7 +368,7 @@ export class PortfolioController {
}); });
if ( if (
impersonationId || hasReadRestrictedAccessPermission ||
this.request.user.Settings.settings.viewMode === 'ZEN' || this.request.user.Settings.settings.viewMode === 'ZEN' ||
this.userService.isRestrictedView(this.request.user) this.userService.isRestrictedView(this.request.user)
) { ) {

25
apps/api/src/app/user/user.service.ts

@ -105,18 +105,33 @@ export class UserService {
return usersWithAdminRole.length > 0; return usersWithAdminRole.length > 0;
} }
public isRestrictedView(aUser: UserWithSettings) { public hasReadRestrictedAccessPermission({
return aUser.Settings.settings.isRestrictedView ?? false; impersonationId,
user
}: {
impersonationId: string;
user: UserWithSettings;
}) {
if (!impersonationId) {
return false;
}
const access = user.Access?.find(({ id }) => {
return id === impersonationId;
});
return access?.permissions?.includes('READ_RESTRICTED') ?? true;
} }
public hasReadRestrictedPermission(user: UserWithSettings): boolean { public isRestrictedView(aUser: UserWithSettings) {
return user.permissions.includes('READ_RESTRICTED'); return aUser.Settings.settings.isRestrictedView ?? false;
} }
public async user( public async user(
userWhereUniqueInput: Prisma.UserWhereUniqueInput userWhereUniqueInput: Prisma.UserWhereUniqueInput
): Promise<UserWithSettings | null> { ): Promise<UserWithSettings | null> {
const { const {
Access,
accessToken, accessToken,
Account, Account,
Analytics, Analytics,
@ -131,6 +146,7 @@ export class UserService {
updatedAt updatedAt
} = await this.prismaService.user.findUnique({ } = await this.prismaService.user.findUnique({
include: { include: {
Access: true,
Account: { Account: {
include: { Platform: true } include: { Platform: true }
}, },
@ -142,6 +158,7 @@ export class UserService {
}); });
const user: UserWithSettings = { const user: UserWithSettings = {
Access,
accessToken, accessToken,
Account, Account,
authChallenge, authChallenge,

18
apps/api/src/interceptors/redact-values-in-response.interceptor.ts

@ -1,6 +1,7 @@
import { UserService } from '@ghostfolio/api/app/user/user.service'; import { UserService } from '@ghostfolio/api/app/user/user.service';
import { redactAttributes } from '@ghostfolio/api/helper/object.helper'; import { redactAttributes } from '@ghostfolio/api/helper/object.helper';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config'; import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { UserWithSettings } from '@ghostfolio/common/types';
import { import {
CallHandler, CallHandler,
ExecutionContext, ExecutionContext,
@ -22,13 +23,20 @@ export class RedactValuesInResponseInterceptor<T>
): Observable<any> { ): Observable<any> {
return next.handle().pipe( return next.handle().pipe(
map((data: any) => { map((data: any) => {
const request = context.switchToHttp().getRequest(); const { headers, user }: { headers: Headers; user: UserWithSettings } =
const hasImpersonationId = context.switchToHttp().getRequest();
!!request.headers?.[HEADER_KEY_IMPERSONATION.toLowerCase()];
const impersonationId =
headers?.[HEADER_KEY_IMPERSONATION.toLowerCase()];
const hasReadRestrictedPermission =
this.userService.hasReadRestrictedAccessPermission({
impersonationId,
user
});
if ( if (
hasImpersonationId || hasReadRestrictedPermission ||
this.userService.hasReadRestrictedPermission(request.user) this.userService.isRestrictedView(user)
) { ) {
data = redactAttributes({ data = redactAttributes({
object: data, object: data,

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

@ -33,7 +33,9 @@
<mat-label i18n>Permission</mat-label> <mat-label i18n>Permission</mat-label>
<mat-select formControlName="permissions"> <mat-select formControlName="permissions">
<mat-option i18n value="READ_RESTRICTED">Restricted view</mat-option> <mat-option i18n value="READ_RESTRICTED">Restricted view</mat-option>
@if(data?.user?.settings?.isExperimentalFeatures) {
<mat-option i18n value="READ">View</mat-option> <mat-option i18n value="READ">View</mat-option>
}
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>

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

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

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

@ -7,7 +7,6 @@ import {
} from '@angular/core'; } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router } from '@angular/router'; 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 { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Access, User } from '@ghostfolio/common/interfaces'; import { Access, User } from '@ghostfolio/common/interfaces';
@ -107,7 +106,8 @@ export class UserAccountAccessComponent implements OnDestroy, OnInit {
alias: '', alias: '',
permissions: ['READ_RESTRICTED'], permissions: ['READ_RESTRICTED'],
type: 'PRIVATE' type: 'PRIVATE'
} },
user: this.user
}, },
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh', height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'

3
libs/common/src/lib/types/user-with-settings.type.ts

@ -1,10 +1,11 @@
import { UserSettings } from '@ghostfolio/common/interfaces'; import { UserSettings } from '@ghostfolio/common/interfaces';
import { SubscriptionOffer } from '@ghostfolio/common/types'; import { SubscriptionOffer } from '@ghostfolio/common/types';
import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type'; import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type';
import { Account, Settings, User } from '@prisma/client'; import { Access, Account, Settings, User } from '@prisma/client';
// TODO: Compare with User interface // TODO: Compare with User interface
export type UserWithSettings = User & { export type UserWithSettings = User & {
Access: Access[];
Account: Account[]; Account: Account[];
activityCount: number; activityCount: number;
permissions?: string[]; permissions?: string[];

Loading…
Cancel
Save