Feature/add restricted view (#295)
* Add restricted view * Update changelog
This commit is contained in:
parent
7c91727eb1
commit
05b0efef82
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added an option to hide absolute values like performances and quantities (_Restricted View_)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Restructured the allocations page
|
- Restructured the allocations page
|
||||||
@ -21,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Removed the current net performance
|
- Removed the current net performance
|
||||||
- Removed the read foreign portfolio permission
|
- Removed the read foreign portfolio permission
|
||||||
|
|
||||||
|
### Todo
|
||||||
|
|
||||||
|
- Apply data migration (`yarn database:push`)
|
||||||
|
|
||||||
## 1.38.0 - 14.08.2021
|
## 1.38.0 - 14.08.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
|
import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import {
|
import {
|
||||||
@ -33,7 +34,8 @@ export class AccountController {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
|
private readonly userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@ -94,7 +96,10 @@ export class AccountController {
|
|||||||
impersonationUserId || this.request.user.id
|
impersonationUserId || this.request.user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
if (impersonationUserId) {
|
if (
|
||||||
|
impersonationUserId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
accounts = nullifyValuesInObjects(accounts, [
|
accounts = nullifyValuesInObjects(accounts, [
|
||||||
'balance',
|
'balance',
|
||||||
'fee',
|
'fee',
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
|
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
|
||||||
@ -16,7 +17,8 @@ import { AccountService } from './account.service';
|
|||||||
ExchangeRateDataModule,
|
ExchangeRateDataModule,
|
||||||
ImpersonationModule,
|
ImpersonationModule,
|
||||||
RedisCacheModule,
|
RedisCacheModule,
|
||||||
PrismaModule
|
PrismaModule,
|
||||||
|
UserModule
|
||||||
],
|
],
|
||||||
controllers: [AccountController],
|
controllers: [AccountController],
|
||||||
providers: [AccountService]
|
providers: [AccountService]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
|
import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||||
import {
|
import {
|
||||||
@ -34,7 +35,8 @@ export class OrderController {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly impersonationService: ImpersonationService,
|
private readonly impersonationService: ImpersonationService,
|
||||||
private readonly orderService: OrderService,
|
private readonly orderService: OrderService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
|
private readonly userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@ -88,7 +90,10 @@ export class OrderController {
|
|||||||
where: { userId: impersonationUserId || this.request.user.id }
|
where: { userId: impersonationUserId || this.request.user.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (impersonationUserId) {
|
if (
|
||||||
|
impersonationUserId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
orders = nullifyValuesInObjects(orders, ['fee', 'quantity', 'unitPrice']);
|
orders = nullifyValuesInObjects(orders, ['fee', 'quantity', 'unitPrice']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
import { CacheService } from '@ghostfolio/api/app/cache/cache.service';
|
||||||
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
|
||||||
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
@ -17,7 +18,8 @@ import { OrderService } from './order.service';
|
|||||||
DataProviderModule,
|
DataProviderModule,
|
||||||
ImpersonationModule,
|
ImpersonationModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
RedisCacheModule
|
RedisCacheModule,
|
||||||
|
UserModule
|
||||||
],
|
],
|
||||||
controllers: [OrderController],
|
controllers: [OrderController],
|
||||||
providers: [CacheService, OrderService],
|
providers: [CacheService, OrderService],
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
||||||
import {
|
import {
|
||||||
hasNotDefinedValuesInObject,
|
hasNotDefinedValuesInObject,
|
||||||
nullifyValuesInObject
|
nullifyValuesInObject
|
||||||
} from '@ghostfolio/api/helper/object.helper';
|
} from '@ghostfolio/api/helper/object.helper';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
|
||||||
import {
|
import {
|
||||||
PortfolioPerformance,
|
PortfolioPerformance,
|
||||||
PortfolioPosition,
|
PortfolioPosition,
|
||||||
@ -39,9 +39,9 @@ import { PortfolioService } from './portfolio.service';
|
|||||||
export class PortfolioController {
|
export class PortfolioController {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly impersonationService: ImpersonationService,
|
|
||||||
private readonly portfolioService: PortfolioService,
|
private readonly portfolioService: PortfolioService,
|
||||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
@Inject(REQUEST) private readonly request: RequestWithUser,
|
||||||
|
private readonly userService: UserService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('investments')
|
@Get('investments')
|
||||||
@ -53,7 +53,10 @@ export class PortfolioController {
|
|||||||
impersonationId
|
impersonationId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
const maxInvestment = investments.reduce(
|
const maxInvestment = investments.reduce(
|
||||||
(investment, item) => Math.max(investment, item.investment),
|
(investment, item) => Math.max(investment, item.investment),
|
||||||
1
|
1
|
||||||
@ -92,7 +95,10 @@ export class PortfolioController {
|
|||||||
res.status(StatusCodes.ACCEPTED);
|
res.status(StatusCodes.ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
let maxValue = 0;
|
let maxValue = 0;
|
||||||
|
|
||||||
chartData.forEach((portfolioItem) => {
|
chartData.forEach((portfolioItem) => {
|
||||||
@ -133,7 +139,10 @@ export class PortfolioController {
|
|||||||
res.status(StatusCodes.ACCEPTED);
|
res.status(StatusCodes.ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
const totalInvestment = Object.values(details)
|
const totalInvestment = Object.values(details)
|
||||||
.map((portfolioPosition) => {
|
.map((portfolioPosition) => {
|
||||||
return portfolioPosition.investment;
|
return portfolioPosition.investment;
|
||||||
@ -187,7 +196,10 @@ export class PortfolioController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let performance = performanceInformation.performance;
|
let performance = performanceInformation.performance;
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
performance = nullifyValuesInObject(performance, [
|
performance = nullifyValuesInObject(performance, [
|
||||||
'currentGrossPerformance',
|
'currentGrossPerformance',
|
||||||
'currentValue'
|
'currentValue'
|
||||||
@ -213,7 +225,10 @@ export class PortfolioController {
|
|||||||
res.status(StatusCodes.ACCEPTED);
|
res.status(StatusCodes.ACCEPTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
result.positions = result.positions.map((position) => {
|
result.positions = result.positions.map((position) => {
|
||||||
return nullifyValuesInObject(position, [
|
return nullifyValuesInObject(position, [
|
||||||
'grossPerformance',
|
'grossPerformance',
|
||||||
@ -233,7 +248,10 @@ export class PortfolioController {
|
|||||||
): Promise<PortfolioSummary> {
|
): Promise<PortfolioSummary> {
|
||||||
let summary = await this.portfolioService.getSummary(impersonationId);
|
let summary = await this.portfolioService.getSummary(impersonationId);
|
||||||
|
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
summary = nullifyValuesInObject(summary, [
|
summary = nullifyValuesInObject(summary, [
|
||||||
'cash',
|
'cash',
|
||||||
'committedFunds',
|
'committedFunds',
|
||||||
@ -261,7 +279,10 @@ export class PortfolioController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (position) {
|
if (position) {
|
||||||
if (impersonationId) {
|
if (
|
||||||
|
impersonationId ||
|
||||||
|
this.userService.isRestrictedView(this.request.user)
|
||||||
|
) {
|
||||||
position = nullifyValuesInObject(position, [
|
position = nullifyValuesInObject(position, [
|
||||||
'grossPerformance',
|
'grossPerformance',
|
||||||
'investment',
|
'investment',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
|
||||||
import { UserService } from '@ghostfolio/api/app/user/user.service';
|
import { UserModule } from '@ghostfolio/api/app/user/user.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module';
|
||||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering.module';
|
||||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||||
@ -24,7 +24,8 @@ import { RulesService } from './rules.service';
|
|||||||
ExchangeRateDataModule,
|
ExchangeRateDataModule,
|
||||||
ImpersonationModule,
|
ImpersonationModule,
|
||||||
OrderModule,
|
OrderModule,
|
||||||
PrismaModule
|
PrismaModule,
|
||||||
|
UserModule
|
||||||
],
|
],
|
||||||
controllers: [PortfolioController],
|
controllers: [PortfolioController],
|
||||||
providers: [
|
providers: [
|
||||||
@ -33,8 +34,7 @@ import { RulesService } from './rules.service';
|
|||||||
MarketDataService,
|
MarketDataService,
|
||||||
PortfolioService,
|
PortfolioService,
|
||||||
RulesService,
|
RulesService,
|
||||||
SymbolProfileService,
|
SymbolProfileService
|
||||||
UserService
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class PortfolioModule {}
|
export class PortfolioModule {}
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
export interface UserSettings {
|
||||||
|
isRestrictedView?: boolean;
|
||||||
|
}
|
6
apps/api/src/app/user/update-user-setting.dto.ts
Normal file
6
apps/api/src/app/user/update-user-setting.dto.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { IsBoolean } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateUserSettingDto {
|
||||||
|
@IsBoolean()
|
||||||
|
isRestrictedView?: boolean;
|
||||||
|
}
|
@ -26,6 +26,8 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
|||||||
|
|
||||||
import { UserItem } from './interfaces/user-item.interface';
|
import { UserItem } from './interfaces/user-item.interface';
|
||||||
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
||||||
|
import { UserSettings } from './interfaces/user-settings.interface';
|
||||||
|
import { UpdateUserSettingDto } from './update-user-setting.dto';
|
||||||
import { UpdateUserSettingsDto } from './update-user-settings.dto';
|
import { UpdateUserSettingsDto } from './update-user-settings.dto';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
@ -78,6 +80,32 @@ export class UserController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Put('setting')
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
public async updateUserSetting(@Body() data: UpdateUserSettingDto) {
|
||||||
|
if (
|
||||||
|
!hasPermission(
|
||||||
|
getPermissions(this.request.user.role),
|
||||||
|
permissions.updateUserSettings
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new HttpException(
|
||||||
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
|
StatusCodes.FORBIDDEN
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userSettings: UserSettings = {
|
||||||
|
...(<UserSettings>this.request.user.Settings.settings),
|
||||||
|
...data
|
||||||
|
};
|
||||||
|
|
||||||
|
return await this.userService.updateUserSetting({
|
||||||
|
userSettings,
|
||||||
|
userId: this.request.user.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Put('settings')
|
@Put('settings')
|
||||||
@UseGuards(AuthGuard('jwt'))
|
@UseGuards(AuthGuard('jwt'))
|
||||||
public async updateUserSettings(@Body() data: UpdateUserSettingsDto) {
|
public async updateUserSettings(@Body() data: UpdateUserSettingsDto) {
|
||||||
|
@ -14,6 +14,7 @@ import { UserService } from './user.service';
|
|||||||
})
|
})
|
||||||
],
|
],
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
providers: [ConfigurationService, PrismaService, UserService]
|
providers: [ConfigurationService, PrismaService, UserService],
|
||||||
|
exports: [UserService]
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
@ -9,6 +9,7 @@ import { Currency, Prisma, Provider, User, ViewMode } from '@prisma/client';
|
|||||||
import { isBefore } from 'date-fns';
|
import { isBefore } from 'date-fns';
|
||||||
|
|
||||||
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
import { UserSettingsParams } from './interfaces/user-settings-params.interface';
|
||||||
|
import { UserSettings } from './interfaces/user-settings.interface';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
@ -50,6 +51,7 @@ export class UserService {
|
|||||||
}),
|
}),
|
||||||
accounts: Account,
|
accounts: Account,
|
||||||
settings: {
|
settings: {
|
||||||
|
...(<UserSettings>Settings.settings),
|
||||||
locale,
|
locale,
|
||||||
baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
|
baseCurrency: Settings?.currency ?? UserService.DEFAULT_CURRENCY,
|
||||||
viewMode: Settings?.viewMode ?? ViewMode.DEFAULT
|
viewMode: Settings?.viewMode ?? ViewMode.DEFAULT
|
||||||
@ -57,6 +59,10 @@ export class UserService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isRestrictedView(aUser: UserWithSettings) {
|
||||||
|
return (aUser.Settings.settings as UserSettings)?.isRestrictedView ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
public async user(
|
public async user(
|
||||||
userWhereUniqueInput: Prisma.UserWhereUniqueInput
|
userWhereUniqueInput: Prisma.UserWhereUniqueInput
|
||||||
): Promise<UserWithSettings | null> {
|
): Promise<UserWithSettings | null> {
|
||||||
@ -84,6 +90,7 @@ export class UserService {
|
|||||||
// Set default settings if needed
|
// Set default settings if needed
|
||||||
userFromDatabase.Settings = {
|
userFromDatabase.Settings = {
|
||||||
currency: UserService.DEFAULT_CURRENCY,
|
currency: UserService.DEFAULT_CURRENCY,
|
||||||
|
settings: null,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
userId: userFromDatabase?.id,
|
userId: userFromDatabase?.id,
|
||||||
viewMode: ViewMode.DEFAULT
|
viewMode: ViewMode.DEFAULT
|
||||||
@ -219,6 +226,35 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateUserSetting({
|
||||||
|
userId,
|
||||||
|
userSettings
|
||||||
|
}: {
|
||||||
|
userId: string;
|
||||||
|
userSettings: UserSettings;
|
||||||
|
}) {
|
||||||
|
const settings = userSettings as Prisma.JsonObject;
|
||||||
|
|
||||||
|
await this.prismaService.settings.upsert({
|
||||||
|
create: {
|
||||||
|
settings,
|
||||||
|
User: {
|
||||||
|
connect: {
|
||||||
|
id: userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
settings
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
userId: userId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
public async updateUserSettings({
|
public async updateUserSettings({
|
||||||
currency,
|
currency,
|
||||||
userId,
|
userId,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<th *matHeaderCellDef class="px-1" i18n mat-header-cell>Type</th>
|
<th *matHeaderCellDef class="px-1" i18n mat-header-cell>Type</th>
|
||||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
<ion-icon class="mr-1" name="lock-closed-outline"></ion-icon>
|
<ion-icon class="mr-1" name="lock-closed-outline"></ion-icon>
|
||||||
Restricted Access
|
Restricted View
|
||||||
</td></ng-container
|
</td></ng-container
|
||||||
>
|
>
|
||||||
|
|
||||||
|
@ -136,6 +136,24 @@ export class AccountPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onRestrictedViewChange(aEvent: MatSlideToggleChange) {
|
||||||
|
this.dataService
|
||||||
|
.putUserSetting({ isRestrictedView: aEvent.checked })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.userService.remove();
|
||||||
|
|
||||||
|
this.userService
|
||||||
|
.get()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((user) => {
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) {
|
public onSignInWithFingerprintChange(aEvent: MatSlideToggleChange) {
|
||||||
if (aEvent.checked) {
|
if (aEvent.checked) {
|
||||||
this.registerDevice();
|
this.registerDevice();
|
||||||
|
@ -50,6 +50,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="align-items-center d-flex mt-4 py-1">
|
||||||
|
<div class="w-50">
|
||||||
|
<div i18n>Restricted View</div>
|
||||||
|
<div class="hint-text text-muted" i18n>
|
||||||
|
Hides absolute values like performances and quantities.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="w-50">
|
||||||
|
<mat-slide-toggle
|
||||||
|
color="primary"
|
||||||
|
[checked]="user.settings.isRestrictedView"
|
||||||
|
[disabled]="!hasPermissionToUpdateUserSettings"
|
||||||
|
(change)="onRestrictedViewChange($event)"
|
||||||
|
></mat-slide-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="d-flex mt-4 py-1">
|
<div class="d-flex mt-4 py-1">
|
||||||
<form #changeUserSettingsForm="ngForm" class="w-100">
|
<form #changeUserSettingsForm="ngForm" class="w-100">
|
||||||
<div class="d-flex mb-2">
|
<div class="d-flex mb-2">
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: block;
|
||||||
|
|
||||||
|
.hint-text {
|
||||||
|
font-size: 90%;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
.mat-form-field {
|
.mat-form-field {
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
.mat-form-field-wrapper {
|
.mat-form-field-wrapper {
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
[isLoading]="isLoadingPerformance"
|
[isLoading]="isLoadingPerformance"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[performance]="performance"
|
[performance]="performance"
|
||||||
[showDetails]="!hasImpersonationId"
|
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
|
||||||
></gf-portfolio-performance>
|
></gf-portfolio-performance>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<gf-toggle
|
<gf-toggle
|
||||||
|
@ -49,7 +49,7 @@
|
|||||||
[isLoading]="isLoadingPerformance"
|
[isLoading]="isLoadingPerformance"
|
||||||
[locale]="user?.settings?.locale"
|
[locale]="user?.settings?.locale"
|
||||||
[performance]="performance"
|
[performance]="performance"
|
||||||
[showDetails]="!hasImpersonationId"
|
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
|
||||||
></gf-portfolio-performance>
|
></gf-portfolio-performance>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,6 +13,7 @@ import { PortfolioPositions } from '@ghostfolio/api/app/portfolio/interfaces/por
|
|||||||
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
|
||||||
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
|
import { SymbolItem } from '@ghostfolio/api/app/symbol/interfaces/symbol-item.interface';
|
||||||
import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface';
|
import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface';
|
||||||
|
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
|
||||||
import { UpdateUserSettingsDto } from '@ghostfolio/api/app/user/update-user-settings.dto';
|
import { UpdateUserSettingsDto } from '@ghostfolio/api/app/user/update-user-settings.dto';
|
||||||
import {
|
import {
|
||||||
Access,
|
Access,
|
||||||
@ -210,6 +211,10 @@ export class DataService {
|
|||||||
return this.http.put<UserItem>(`/api/order/${aOrder.id}`, aOrder);
|
return this.http.put<UserItem>(`/api/order/${aOrder.id}`, aOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public putUserSetting(aData: UpdateUserSettingDto) {
|
||||||
|
return this.http.put<User>(`/api/user/setting`, aData);
|
||||||
|
}
|
||||||
|
|
||||||
public putUserSettings(aData: UpdateUserSettingsDto) {
|
public putUserSettings(aData: UpdateUserSettingsDto) {
|
||||||
return this.http.put<User>(`/api/user/settings`, aData);
|
return this.http.put<User>(`/api/user/settings`, aData);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { Currency, ViewMode } from '@prisma/client';
|
|||||||
|
|
||||||
export interface UserSettings {
|
export interface UserSettings {
|
||||||
baseCurrency?: Currency;
|
baseCurrency?: Currency;
|
||||||
|
isRestrictedView?: boolean;
|
||||||
locale: string;
|
locale: string;
|
||||||
viewMode?: ViewMode;
|
viewMode?: ViewMode;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Settings" ADD COLUMN "settings" JSONB;
|
@ -109,8 +109,9 @@ model Property {
|
|||||||
|
|
||||||
model Settings {
|
model Settings {
|
||||||
currency Currency?
|
currency Currency?
|
||||||
viewMode ViewMode?
|
settings Json?
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
viewMode ViewMode?
|
||||||
User User @relation(fields: [userId], references: [id])
|
User User @relation(fields: [userId], references: [id])
|
||||||
userId String @id
|
userId String @id
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user