Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror

This commit is contained in:
ksyasuda 2024-10-26 07:42:30 -07:00
commit 8e2e73137c
72 changed files with 257 additions and 237 deletions

View File

@ -142,13 +142,8 @@
// The following rules are part of @typescript-eslint/stylistic-type-checked // The following rules are part of @typescript-eslint/stylistic-type-checked
// and can be remove once solved // and can be remove once solved
"@typescript-eslint/consistent-type-definitions": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-nullish-coalescing": "warn", // TODO: Requires strictNullChecks: true "@typescript-eslint/prefer-nullish-coalescing": "warn", // TODO: Requires strictNullChecks: true
"@typescript-eslint/consistent-type-assertions": "warn", "@typescript-eslint/consistent-indexed-object-style": "warn"
"@typescript-eslint/prefer-optional-chain": "warn",
"@typescript-eslint/consistent-indexed-object-style": "warn",
"@typescript-eslint/consistent-generic-constructors": "warn"
} }
} }
], ],

View File

@ -9,13 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Switched the `consistent-generic-constructors` rule from `warn` to `error` in the `eslint` configuration
- Switched the `consistent-type-assertions` rule from `warn` to `error` in the `eslint` configuration
- Switched the `prefer-optional-chain` rule from `warn` to `error` in the `eslint` configuration
## 2.119.0 - 2024-10-26
### Changed
- Switched the `consistent-type-definitions` rule from `warn` to `error` in the `eslint` configuration
- Switched the `no-empty-function` rule from `warn` to `error` in the `eslint` configuration - Switched the `no-empty-function` rule from `warn` to `error` in the `eslint` configuration
- Switched the `prefer-function-type` rule from `warn` to `error` in the `eslint` configuration
- Upgraded `prisma` from version `5.20.0` to `5.21.1`
### Fixed ### Fixed
- Fixed an issue with the X-axis scale of the dividend timeline on the analysis page - Fixed an issue with the X-axis scale of the dividend timeline on the analysis page
- Fixed an issue with the X-axis scale of the investment timeline on the analysis page - Fixed an issue with the X-axis scale of the investment timeline on the analysis page
- Fixed an issue with the X-axis scale of the portfolio evolution chart on the analysis page - Fixed an issue with the X-axis scale of the portfolio evolution chart on the analysis page
- Fixed an issue in the calculation of the static portfolio analysis rule: Allocation Cluster Risk (Developed Markets)
- Fixed an issue in the calculation of the static portfolio analysis rule: Allocation Cluster Risk (Emerging Markets)
## 2.118.0 - 2024-10-23 ## 2.118.0 - 2024-10-23

View File

@ -88,7 +88,7 @@ export class AccountBalanceService {
this.eventEmitter.emit( this.eventEmitter.emit(
PortfolioChangedEvent.getName(), PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({ new PortfolioChangedEvent({
userId: <string>where.userId userId: where.userId as string
}) })
); );

View File

@ -209,8 +209,8 @@ export class AccountService {
const { data, where } = params; const { data, where } = params;
await this.accountBalanceService.createOrUpdateAccountBalance({ await this.accountBalanceService.createOrUpdateAccountBalance({
accountId: <string>data.id, accountId: data.id as string,
balance: <number>data.balance, balance: data.balance as number,
date: format(new Date(), DATE_FORMAT), date: format(new Date(), DATE_FORMAT),
userId: aUserId userId: aUserId
}); });

View File

@ -26,7 +26,7 @@ export class QueueController {
public async deleteJobs( public async deleteJobs(
@Query('status') filterByStatus?: string @Query('status') filterByStatus?: string
): Promise<void> { ): Promise<void> {
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined; const status = (filterByStatus?.split(',') as JobStatus[]) ?? undefined;
return this.queueService.deleteJobs({ status }); return this.queueService.deleteJobs({ status });
} }
@ -36,7 +36,7 @@ export class QueueController {
public async getJobs( public async getJobs(
@Query('status') filterByStatus?: string @Query('status') filterByStatus?: string
): Promise<AdminJobs> { ): Promise<AdminJobs> {
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined; const status = (filterByStatus?.split(',') as JobStatus[]) ?? undefined;
return this.queueService.getJobs({ status }); return this.queueService.getJobs({ status });
} }

View File

@ -85,7 +85,7 @@ export class AuthController {
@Res() response: Response @Res() response: Response
) { ) {
// Handles the Google OAuth2 callback // Handles the Google OAuth2 callback
const jwt: string = (<any>request.user).jwt; const jwt: string = (request.user as any).jwt;
if (jwt) { if (jwt) {
response.redirect( response.redirect(

View File

@ -198,12 +198,12 @@ export interface AuthenticatorAssertionResponseJSON
/** /**
* A WebAuthn-compatible device and the information needed to verify assertions by it * A WebAuthn-compatible device and the information needed to verify assertions by it
*/ */
export declare type AuthenticatorDevice = { export declare interface AuthenticatorDevice {
credentialPublicKey: Buffer; credentialPublicKey: Buffer;
credentialID: Buffer; credentialID: Buffer;
counter: number; counter: number;
transports?: AuthenticatorTransport[]; transports?: AuthenticatorTransport[];
}; }
/** /**
* An attempt to communicate that this isn't just any string, but a Base64URL-encoded string * An attempt to communicate that this isn't just any string, but a Base64URL-encoded string
*/ */

View File

@ -442,10 +442,10 @@ export class BenchmarkService {
await this.redisCacheService.set( await this.redisCacheService.set(
this.CACHE_KEY_BENCHMARKS, this.CACHE_KEY_BENCHMARKS,
JSON.stringify(<BenchmarkValue>{ JSON.stringify({
benchmarks, benchmarks,
expiration: expiration.getTime() expiration: expiration.getTime()
}), } as BenchmarkValue),
CACHE_TTL_INFINITE CACHE_TTL_INFINITE
); );
} }

View File

@ -410,7 +410,7 @@ export class OrderService {
where.SymbolProfile, where.SymbolProfile,
{ {
AND: [ AND: [
{ dataSource: <DataSource>filterByDataSource }, { dataSource: filterByDataSource as DataSource },
{ symbol: filterBySymbol } { symbol: filterBySymbol }
] ]
} }
@ -419,7 +419,7 @@ export class OrderService {
} else { } else {
where.SymbolProfile = { where.SymbolProfile = {
AND: [ AND: [
{ dataSource: <DataSource>filterByDataSource }, { dataSource: filterByDataSource as DataSource },
{ symbol: filterBySymbol } { symbol: filterBySymbol }
] ]
}; };
@ -638,7 +638,7 @@ export class OrderService {
{ {
dataSource: dataSource:
data.SymbolProfile.connect.dataSource_symbol.dataSource, data.SymbolProfile.connect.dataSource_symbol.dataSource,
date: <Date>data.date, date: data.date as Date,
symbol: data.SymbolProfile.connect.dataSource_symbol.symbol symbol: data.SymbolProfile.connect.dataSource_symbol.symbol
} }
], ],

View File

@ -796,7 +796,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
[key: DateRange]: Big; [key: DateRange]: Big;
} = {}; } = {};
for (const dateRange of <DateRange[]>[ for (const dateRange of [
'1d', '1d',
'1y', '1y',
'5y', '5y',
@ -812,7 +812,7 @@ export class TWRPortfolioCalculator extends PortfolioCalculator {
// .map((date) => { // .map((date) => {
// return format(date, 'yyyy'); // return format(date, 'yyyy');
// }) // })
]) { ] as DateRange[]) {
const dateInterval = getIntervalFromDateRange(dateRange); const dateInterval = getIntervalFromDateRange(dateRange);
const endDate = dateInterval.endDate; const endDate = dateInterval.endDate;
let startDate = dateInterval.startDate; let startDate = dateInterval.startDate;

View File

@ -138,7 +138,7 @@ export class PortfolioService {
some: { some: {
SymbolProfile: { SymbolProfile: {
AND: [ AND: [
{ dataSource: <DataSource>filterByDataSource }, { dataSource: filterByDataSource as DataSource },
{ symbol: filterBySymbol } { symbol: filterBySymbol }
] ]
} }
@ -1160,7 +1160,7 @@ export class PortfolioService {
public async getReport(impersonationId: string): Promise<PortfolioReport> { public async getReport(impersonationId: string): Promise<PortfolioReport> {
const userId = await this.getUserId(impersonationId, this.request.user.id); const userId = await this.getUserId(impersonationId, this.request.user.id);
const userSettings = <UserSettings>this.request.user.Settings.settings; const userSettings = this.request.user.Settings.settings as UserSettings;
const { accounts, holdings, markets, summary } = await this.getDetails({ const { accounts, holdings, markets, summary } = await this.getDetails({
impersonationId, impersonationId,
@ -1169,6 +1169,12 @@ export class PortfolioService {
withSummary: true withSummary: true
}); });
const marketsTotalInBaseCurrency = getSum(
Object.values(markets).map(({ valueInBaseCurrency }) => {
return new Big(valueInBaseCurrency);
})
).toNumber();
return { return {
rules: { rules: {
accountClusterRisk: accountClusterRisk:
@ -1193,12 +1199,12 @@ export class PortfolioService {
[ [
new AllocationClusterRiskDevelopedMarkets( new AllocationClusterRiskDevelopedMarkets(
this.exchangeRateDataService, this.exchangeRateDataService,
summary.currentValueInBaseCurrency, marketsTotalInBaseCurrency,
markets.developedMarkets.valueInBaseCurrency markets.developedMarkets.valueInBaseCurrency
), ),
new AllocationClusterRiskEmergingMarkets( new AllocationClusterRiskEmergingMarkets(
this.exchangeRateDataService, this.exchangeRateDataService,
summary.currentValueInBaseCurrency, marketsTotalInBaseCurrency,
markets.emergingMarkets.valueInBaseCurrency markets.emergingMarkets.valueInBaseCurrency
) )
], ],
@ -1358,20 +1364,20 @@ export class PortfolioService {
} }
} }
const marketsTotal = const marketsTotalInBaseCurrency = getSum(
markets.developedMarkets.valueInBaseCurrency + Object.values(markets).map(({ valueInBaseCurrency }) => {
markets.emergingMarkets.valueInBaseCurrency + return new Big(valueInBaseCurrency);
markets.otherMarkets.valueInBaseCurrency + })
markets[UNKNOWN_KEY].valueInBaseCurrency; ).toNumber();
markets.developedMarkets.valueInPercentage = markets.developedMarkets.valueInPercentage =
markets.developedMarkets.valueInBaseCurrency / marketsTotal; markets.developedMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets.emergingMarkets.valueInPercentage = markets.emergingMarkets.valueInPercentage =
markets.emergingMarkets.valueInBaseCurrency / marketsTotal; markets.emergingMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets.otherMarkets.valueInPercentage = markets.otherMarkets.valueInPercentage =
markets.otherMarkets.valueInBaseCurrency / marketsTotal; markets.otherMarkets.valueInBaseCurrency / marketsTotalInBaseCurrency;
markets[UNKNOWN_KEY].valueInPercentage = markets[UNKNOWN_KEY].valueInPercentage =
markets[UNKNOWN_KEY].valueInBaseCurrency / marketsTotal; markets[UNKNOWN_KEY].valueInBaseCurrency / marketsTotalInBaseCurrency;
const marketsAdvancedTotal = const marketsAdvancedTotal =
marketsAdvanced.asiaPacific.valueInBaseCurrency + marketsAdvanced.asiaPacific.valueInBaseCurrency +

View File

@ -19,11 +19,11 @@ import { RedisCacheService } from './redis-cache.service';
configurationService.get('REDIS_PASSWORD') configurationService.get('REDIS_PASSWORD')
); );
return <RedisClientOptions>{ return {
store: redisStore, store: redisStore,
ttl: configurationService.get('CACHE_TTL'), ttl: configurationService.get('CACHE_TTL'),
url: `redis://${redisPassword ? `:${redisPassword}` : ''}@${configurationService.get('REDIS_HOST')}:${configurationService.get('REDIS_PORT')}/${configurationService.get('REDIS_DB')}` url: `redis://${redisPassword ? `:${redisPassword}` : ''}@${configurationService.get('REDIS_HOST')}:${configurationService.get('REDIS_PORT')}/${configurationService.get('REDIS_DB')}`
}; } as RedisClientOptions;
} }
}), }),
ConfigurationModule ConfigurationModule

View File

@ -95,7 +95,7 @@ export class SubscriptionController {
@Res() response: Response @Res() response: Response
) { ) {
const userId = await this.subscriptionService.createSubscriptionViaStripe( const userId = await this.subscriptionService.createSubscriptionViaStripe(
<string>request.query.checkoutSessionId request.query.checkoutSessionId as string
); );
Logger.log( Logger.log(
@ -113,7 +113,7 @@ export class SubscriptionController {
@Post('stripe/checkout-session') @Post('stripe/checkout-session')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createCheckoutSession( public async createCheckoutSession(
@Body() { couponId, priceId }: { couponId: string; priceId: string } @Body() { couponId, priceId }: { couponId?: string; priceId: string }
) { ) {
try { try {
return this.subscriptionService.createCheckoutSession({ return this.subscriptionService.createCheckoutSession({

View File

@ -124,7 +124,9 @@ export class SubscriptionService {
let offer: SubscriptionOffer = price ? 'renewal' : 'default'; let offer: SubscriptionOffer = price ? 'renewal' : 'default';
if (isBefore(createdAt, parseDate('2023-01-01'))) { if (isBefore(createdAt, parseDate('2023-01-01'))) {
offer = 'renewal-early-bird'; offer = 'renewal-early-bird-2023';
} else if (isBefore(createdAt, parseDate('2024-01-01'))) {
offer = 'renewal-early-bird-2024';
} }
return { return {

View File

@ -1,10 +1,10 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { XRayRulesSettings } from '@ghostfolio/common/interfaces';
import type { import type {
ColorScheme, ColorScheme,
DateRange, DateRange,
HoldingsViewMode, HoldingsViewMode,
ViewMode, ViewMode
XRayRulesSettings
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
import { import {
@ -31,11 +31,11 @@ export class UpdateUserSettingDto {
@IsOptional() @IsOptional()
benchmark?: string; benchmark?: string;
@IsIn(<ColorScheme[]>['DARK', 'LIGHT']) @IsIn(['DARK', 'LIGHT'] as ColorScheme[])
@IsOptional() @IsOptional()
colorScheme?: ColorScheme; colorScheme?: ColorScheme;
@IsIn(<DateRange[]>[ @IsIn([
'1d', '1d',
'1y', '1y',
'5y', '5y',
@ -48,7 +48,7 @@ export class UpdateUserSettingDto {
return format(date, 'yyyy'); return format(date, 'yyyy');
} }
) )
]) ] as DateRange[])
@IsOptional() @IsOptional()
dateRange?: DateRange; dateRange?: DateRange;
@ -68,7 +68,7 @@ export class UpdateUserSettingDto {
@IsOptional() @IsOptional()
'filters.tags'?: string[]; 'filters.tags'?: string[];
@IsIn(<HoldingsViewMode[]>['CHART', 'TABLE']) @IsIn(['CHART', 'TABLE'] as HoldingsViewMode[])
@IsOptional() @IsOptional()
holdingsViewMode?: HoldingsViewMode; holdingsViewMode?: HoldingsViewMode;
@ -100,7 +100,7 @@ export class UpdateUserSettingDto {
@IsOptional() @IsOptional()
savingsRate?: number; savingsRate?: number;
@IsIn(<ViewMode[]>['DEFAULT', 'ZEN']) @IsIn(['DEFAULT', 'ZEN'] as ViewMode[])
@IsOptional() @IsOptional()
viewMode?: ViewMode; viewMode?: ViewMode;

View File

@ -148,7 +148,7 @@ export class UserController {
const userSettings: UserSettings = merge( const userSettings: UserSettings = merge(
{}, {},
<UserSettings>this.request.user.Settings.settings, this.request.user.Settings.settings as UserSettings,
data data
); );

View File

@ -116,8 +116,8 @@ export class UserService {
accounts: Account, accounts: Account,
dateOfFirstActivity: firstActivity?.date ?? new Date(), dateOfFirstActivity: firstActivity?.date ?? new Date(),
settings: { settings: {
...(<UserSettings>Settings.settings), ...(Settings.settings as UserSettings),
locale: (<UserSettings>Settings.settings)?.locale ?? aLocale locale: (Settings.settings as UserSettings)?.locale ?? aLocale
} }
}; };
} }

View File

@ -40,7 +40,7 @@ export function redactAttributes({
object: any; object: any;
options: { attribute: string; valueMap: { [key: string]: any } }[]; options: { attribute: string; valueMap: { [key: string]: any } }[];
}): any { }): any {
if (!object || !options || !options.length) { if (!object || !options?.length) {
return object; return object;
} }

View File

@ -34,28 +34,28 @@ export class ApiService {
const filters = [ const filters = [
...accountIds.map((accountId) => { ...accountIds.map((accountId) => {
return <Filter>{ return {
id: accountId, id: accountId,
type: 'ACCOUNT' type: 'ACCOUNT'
}; } as Filter;
}), }),
...assetClasses.map((assetClass) => { ...assetClasses.map((assetClass) => {
return <Filter>{ return {
id: assetClass, id: assetClass,
type: 'ASSET_CLASS' type: 'ASSET_CLASS'
}; } as Filter;
}), }),
...assetSubClasses.map((assetClass) => { ...assetSubClasses.map((assetClass) => {
return <Filter>{ return {
id: assetClass, id: assetClass,
type: 'ASSET_SUB_CLASS' type: 'ASSET_SUB_CLASS'
}; } as Filter;
}), }),
...tagIds.map((tagId) => { ...tagIds.map((tagId) => {
return <Filter>{ return {
id: tagId, id: tagId,
type: 'TAG' type: 'TAG'
}; } as Filter;
}) })
]; ];

View File

@ -144,21 +144,21 @@ export class MarketDataService {
({ dataSource, date, marketPrice, symbol, state }) => { ({ dataSource, date, marketPrice, symbol, state }) => {
return this.prismaService.marketData.upsert({ return this.prismaService.marketData.upsert({
create: { create: {
dataSource: <DataSource>dataSource, dataSource: dataSource as DataSource,
date: <Date>date, date: date as Date,
marketPrice: <number>marketPrice, marketPrice: marketPrice as number,
state: <MarketDataState>state, state: state as MarketDataState,
symbol: <string>symbol symbol: symbol as string
}, },
update: { update: {
marketPrice: <number>marketPrice, marketPrice: marketPrice as number,
state: <MarketDataState>state state: state as MarketDataState
}, },
where: { where: {
dataSource_date_symbol: { dataSource_date_symbol: {
dataSource: <DataSource>dataSource, dataSource: dataSource as DataSource,
date: <Date>date, date: date as Date,
symbol: <string>symbol symbol: symbol as string
} }
} }
}); });

View File

@ -78,7 +78,7 @@ export class DataGatheringProcessor {
public async gatherHistoricalMarketData(job: Job<IDataGatheringItem>) { public async gatherHistoricalMarketData(job: Job<IDataGatheringItem>) {
try { try {
const { dataSource, date, symbol } = job.data; const { dataSource, date, symbol } = job.data;
let currentDate = parseISO(<string>(<unknown>date)); let currentDate = parseISO(date as unknown as string);
Logger.log( Logger.log(
`Historical market data gathering has been started for ${symbol} (${dataSource}) at ${format( `Historical market data gathering has been started for ${symbol} (${dataSource}) at ${format(

View File

@ -94,10 +94,10 @@ export class PortfolioSnapshotProcessor {
filters: job.data.filters, filters: job.data.filters,
userId: job.data.userId userId: job.data.userId
}), }),
JSON.stringify(<PortfolioSnapshotValue>(<unknown>{ JSON.stringify({
expiration: expiration.getTime(), expiration: expiration.getTime(),
portfolioSnapshot: snapshot portfolioSnapshot: snapshot
})), } as unknown as PortfolioSnapshotValue),
CACHE_TTL_INFINITE CACHE_TTL_INFINITE
); );

View File

@ -176,7 +176,7 @@ export class SymbolProfileService {
countries: this.getCountries( countries: this.getCountries(
symbolProfile?.countries as unknown as Prisma.JsonArray symbolProfile?.countries as unknown as Prisma.JsonArray
), ),
dateOfFirstActivity: <Date>undefined, dateOfFirstActivity: undefined as Date,
holdings: this.getHoldings(symbolProfile), holdings: this.getHoldings(symbolProfile),
scraperConfiguration: this.getScraperConfiguration(symbolProfile), scraperConfiguration: this.getScraperConfiguration(symbolProfile),
sectors: this.getSectors(symbolProfile), sectors: this.getSectors(symbolProfile),

View File

@ -292,7 +292,7 @@ export class AppComponent implements OnDestroy, OnInit {
const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, { const dialogRef = this.dialog.open(GfHoldingDetailDialogComponent, {
autoFocus: false, autoFocus: false,
data: <HoldingDetailDialogParams>{ data: {
dataSource, dataSource,
symbol, symbol,
baseCurrency: this.user?.settings?.baseCurrency, baseCurrency: this.user?.settings?.baseCurrency,
@ -312,7 +312,7 @@ export class AppComponent implements OnDestroy, OnInit {
hasPermission(this.user?.permissions, permissions.updateOrder) && hasPermission(this.user?.permissions, permissions.updateOrder) &&
!this.user?.settings?.isRestrictedView, !this.user?.settings?.isRestrictedView,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
}, } as HoldingDetailDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -47,8 +47,7 @@ export class AccountsTableComponent implements OnChanges, OnDestroy {
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<AccountModel> = public dataSource = new MatTableDataSource<AccountModel>();
new MatTableDataSource();
public displayedColumns = []; public displayedColumns = [];
public isLoading = true; public isLoading = true;
public routeQueryParams: Subscription; public routeQueryParams: Subscription;

View File

@ -35,10 +35,10 @@ export class AdminJobsComponent implements OnDestroy, OnInit {
DATA_GATHERING_QUEUE_PRIORITY_HIGH; DATA_GATHERING_QUEUE_PRIORITY_HIGH;
public DATA_GATHERING_QUEUE_PRIORITY_MEDIUM = public DATA_GATHERING_QUEUE_PRIORITY_MEDIUM =
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM; DATA_GATHERING_QUEUE_PRIORITY_MEDIUM;
public dataSource = new MatTableDataSource<AdminJobs['jobs'][0]>();
public defaultDateTimeFormat: string; public defaultDateTimeFormat: string;
public filterForm: FormGroup; public filterForm: FormGroup;
public dataSource: MatTableDataSource<AdminJobs['jobs'][0]> =
new MatTableDataSource();
public displayedColumns = [ public displayedColumns = [
'index', 'index',
'type', 'type',

View File

@ -178,14 +178,14 @@ export class AdminMarketDataDetailComponent implements OnChanges {
const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice; const marketPrice = this.marketDataByMonth[yearMonth]?.[day]?.marketPrice;
const dialogRef = this.dialog.open(MarketDataDetailDialog, { const dialogRef = this.dialog.open(MarketDataDetailDialog, {
data: <MarketDataDetailDialogParams>{ data: {
marketPrice, marketPrice,
currency: this.currency, currency: this.currency,
dataSource: this.dataSource, dataSource: this.dataSource,
dateString: `${yearMonth}-${day}`, dateString: `${yearMonth}-${day}`,
symbol: this.symbol, symbol: this.symbol,
user: this.user user: this.user
}, } as MarketDataDetailDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -71,36 +71,35 @@ export class AdminMarketDataComponent
return { return {
id: assetSubClass.toString(), id: assetSubClass.toString(),
label: translate(assetSubClass), label: translate(assetSubClass),
type: <Filter['type']>'ASSET_SUB_CLASS' type: 'ASSET_SUB_CLASS' as Filter['type']
}; };
}) })
.concat([ .concat([
{ {
id: 'BENCHMARKS', id: 'BENCHMARKS',
label: $localize`Benchmarks`, label: $localize`Benchmarks`,
type: <Filter['type']>'PRESET_ID' type: 'PRESET_ID' as Filter['type']
}, },
{ {
id: 'CURRENCIES', id: 'CURRENCIES',
label: $localize`Currencies`, label: $localize`Currencies`,
type: <Filter['type']>'PRESET_ID' type: 'PRESET_ID' as Filter['type']
}, },
{ {
id: 'ETF_WITHOUT_COUNTRIES', id: 'ETF_WITHOUT_COUNTRIES',
label: $localize`ETFs without Countries`, label: $localize`ETFs without Countries`,
type: <Filter['type']>'PRESET_ID' type: 'PRESET_ID' as Filter['type']
}, },
{ {
id: 'ETF_WITHOUT_SECTORS', id: 'ETF_WITHOUT_SECTORS',
label: $localize`ETFs without Sectors`, label: $localize`ETFs without Sectors`,
type: <Filter['type']>'PRESET_ID' type: 'PRESET_ID' as Filter['type']
} }
]); ]);
public benchmarks: Partial<SymbolProfile>[]; public benchmarks: Partial<SymbolProfile>[];
public currentDataSource: DataSource; public currentDataSource: DataSource;
public currentSymbol: string; public currentSymbol: string;
public dataSource: MatTableDataSource<AdminMarketDataItem> = public dataSource = new MatTableDataSource<AdminMarketDataItem>();
new MatTableDataSource();
public defaultDateFormat: string; public defaultDateFormat: string;
public deviceType: string; public deviceType: string;
public displayedColumns: string[] = []; public displayedColumns: string[] = [];
@ -375,13 +374,13 @@ export class AdminMarketDataComponent
const dialogRef = this.dialog.open(AssetProfileDialog, { const dialogRef = this.dialog.open(AssetProfileDialog, {
autoFocus: false, autoFocus: false,
data: <AssetProfileDialogParams>{ data: {
dataSource, dataSource,
symbol, symbol,
colorScheme: this.user?.settings.colorScheme, colorScheme: this.user?.settings.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
}, } as AssetProfileDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -404,10 +403,10 @@ export class AdminMarketDataComponent
const dialogRef = this.dialog.open(CreateAssetProfileDialog, { const dialogRef = this.dialog.open(CreateAssetProfileDialog, {
autoFocus: false, autoFocus: false,
data: <CreateAssetProfileDialogParams>{ data: {
deviceType: this.deviceType, deviceType: this.deviceType,
locale: this.user?.settings?.locale locale: this.user?.settings?.locale
}, } as CreateAssetProfileDialogParams,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -225,10 +225,10 @@ export class AdminOverviewComponent implements OnDestroy, OnInit {
$localize`Please set your system message:`, $localize`Please set your system message:`,
JSON.stringify( JSON.stringify(
this.systemMessage ?? this.systemMessage ??
<SystemMessage>{ ({
message: '⚒️ Scheduled maintenance in progress...', message: '⚒️ Scheduled maintenance in progress...',
targetGroups: ['Basic', 'Premium'] targetGroups: ['Basic', 'Premium']
} } as SystemMessage)
) )
); );

View File

@ -34,7 +34,7 @@ import { CreateOrUpdatePlatformDialog } from './create-or-update-platform-dialog
export class AdminPlatformComponent implements OnInit, OnDestroy { export class AdminPlatformComponent implements OnInit, OnDestroy {
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<Platform> = new MatTableDataSource(); public dataSource = new MatTableDataSource<Platform>();
public deviceType: string; public deviceType: string;
public displayedColumns = ['name', 'url', 'accounts', 'actions']; public displayedColumns = ['name', 'url', 'accounts', 'actions'];
public platforms: Platform[]; public platforms: Platform[];

View File

@ -34,7 +34,7 @@ import { CreateOrUpdateTagDialog } from './create-or-update-tag-dialog/create-or
export class AdminTagComponent implements OnInit, OnDestroy { export class AdminTagComponent implements OnInit, OnDestroy {
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<Tag> = new MatTableDataSource(); public dataSource = new MatTableDataSource<Tag>();
public deviceType: string; public deviceType: string;
public displayedColumns = ['name', 'userId', 'activities', 'actions']; public displayedColumns = ['name', 'userId', 'activities', 'actions'];
public tags: Tag[]; public tags: Tag[];

View File

@ -24,8 +24,7 @@ import { takeUntil } from 'rxjs/operators';
templateUrl: './admin-users.html' templateUrl: './admin-users.html'
}) })
export class AdminUsersComponent implements OnDestroy, OnInit { export class AdminUsersComponent implements OnDestroy, OnInit {
public dataSource: MatTableDataSource<AdminUsers['users'][0]> = public dataSource = new MatTableDataSource<AdminUsers['users'][0]>();
new MatTableDataSource();
public defaultDateFormat: string; public defaultDateFormat: string;
public displayedColumns: string[] = []; public displayedColumns: string[] = [];
public getEmojiFlag = getEmojiFlag; public getEmojiFlag = getEmojiFlag;

View File

@ -98,7 +98,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
} }
private initialize() { private initialize() {
const benchmarkDataValues: { [date: string]: number } = {}; const benchmarkDataValues: Record<string, number> = {};
for (const { date, value } of this.benchmarkDataItems) { for (const { date, value } of this.benchmarkDataItems) {
benchmarkDataValues[date] = value; benchmarkDataValues[date] = value;
@ -133,9 +133,8 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
if (this.chartCanvas) { if (this.chartCanvas) {
if (this.chart) { if (this.chart) {
this.chart.data = data; this.chart.data = data;
this.chart.options.plugins.tooltip = <unknown>( this.chart.options.plugins.tooltip =
this.getTooltipPluginConfiguration() this.getTooltipPluginConfiguration() as unknown;
);
this.chart.update(); this.chart.update();
} else { } else {
this.chart = new Chart(this.chartCanvas.nativeElement, { this.chart = new Chart(this.chartCanvas.nativeElement, {
@ -154,7 +153,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
}, },
interaction: { intersect: false, mode: 'index' }, interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true, maintainAspectRatio: true,
plugins: <unknown>{ plugins: {
annotation: { annotation: {
annotations: { annotations: {
yAxis: { yAxis: {
@ -173,7 +172,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
verticalHoverLine: { verticalHoverLine: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
} }
}, } as unknown,
responsive: true, responsive: true,
scales: { scales: {
x: { x: {
@ -238,7 +237,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
unit: '%' unit: '%'
}), }),
mode: 'index', mode: 'index',
position: <unknown>'top', position: 'top' as unknown,
xAlign: 'center', xAlign: 'center',
yAlign: 'bottom' yAlign: 'bottom'
}; };

View File

@ -180,7 +180,8 @@
<ng-container i18n>Upgrade Plan</ng-container> <ng-container i18n>Upgrade Plan</ng-container>
} @else if ( } @else if (
user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal' ||
user.subscription.offer === 'renewal-early-bird' user.subscription.offer === 'renewal-early-bird-2023' ||
user.subscription.offer === 'renewal-early-bird-2024'
) { ) {
<ng-container i18n>Renew Plan</ng-container> <ng-container i18n>Renew Plan</ng-container>
} }

View File

@ -148,7 +148,7 @@ export class GfHoldingDetailDialogComponent implements OnDestroy, OnInit {
public ngOnInit() { public ngOnInit() {
this.activityForm = this.formBuilder.group({ this.activityForm = this.formBuilder.group({
tags: <string[]>[] tags: [] as string[]
}); });
const filters: Filter[] = [ const filters: Filter[] = [

View File

@ -4,14 +4,11 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
PortfolioPosition, PortfolioPosition,
ToggleOption,
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { import { HoldingType, HoldingsViewMode } from '@ghostfolio/common/types';
HoldingType,
HoldingsViewMode,
ToggleOption
} from '@ghostfolio/common/types';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';

View File

@ -154,9 +154,8 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
if (this.chartCanvas) { if (this.chartCanvas) {
if (this.chart) { if (this.chart) {
this.chart.data = chartData; this.chart.data = chartData;
this.chart.options.plugins.tooltip = <unknown>( this.chart.options.plugins.tooltip =
this.getTooltipPluginConfiguration() this.getTooltipPluginConfiguration() as unknown;
);
if ( if (
this.savingsRate && this.savingsRate &&
@ -186,7 +185,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
}, },
interaction: { intersect: false, mode: 'index' }, interaction: { intersect: false, mode: 'index' },
maintainAspectRatio: true, maintainAspectRatio: true,
plugins: <unknown>{ plugins: {
annotation: { annotation: {
annotations: { annotations: {
savingsRate: this.savingsRate savingsRate: this.savingsRate
@ -227,7 +226,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
verticalHoverLine: { verticalHoverLine: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
} }
}, } as unknown,
responsive: true, responsive: true,
scales: { scales: {
x: { x: {
@ -294,7 +293,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
unit: this.isInPercent ? '%' : undefined unit: this.isInPercent ? '%' : undefined
}), }),
mode: 'index', mode: 'index',
position: <unknown>'top', position: 'top' as unknown,
xAlign: 'center', xAlign: 'center',
yAlign: 'bottom' yAlign: 'bottom'
}; };

View File

@ -1,5 +1,7 @@
import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; import {
import { XRayRulesSettings } from '@ghostfolio/common/types'; PortfolioReportRule,
XRayRulesSettings
} from '@ghostfolio/common/interfaces';
export interface IRuleSettingsDialogParams { export interface IRuleSettingsDialogParams {
rule: PortfolioReportRule; rule: PortfolioReportRule;

View File

@ -1,4 +1,4 @@
import { XRayRulesSettings } from '@ghostfolio/common/types'; import { XRayRulesSettings } from '@ghostfolio/common/interfaces';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Component, Inject } from '@angular/core'; import { Component, Inject } from '@angular/core';

View File

@ -1,7 +1,9 @@
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface'; import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; import {
import { XRayRulesSettings } from '@ghostfolio/common/types'; PortfolioReportRule,
XRayRulesSettings
} from '@ghostfolio/common/interfaces';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,

View File

@ -1,6 +1,8 @@
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; import {
import { XRayRulesSettings } from '@ghostfolio/common/types'; PortfolioReportRule,
XRayRulesSettings
} from '@ghostfolio/common/interfaces';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,

View File

@ -1,4 +1,4 @@
import { ToggleOption } from '@ghostfolio/common/types'; import { ToggleOption } from '@ghostfolio/common/interfaces';
import { import {
ChangeDetectionStrategy, ChangeDetectionStrategy,

View File

@ -16,7 +16,8 @@
<ng-container i18n>Upgrade Plan</ng-container> <ng-container i18n>Upgrade Plan</ng-container>
} @else if ( } @else if (
user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal' ||
user.subscription.offer === 'renewal-early-bird' user.subscription.offer === 'renewal-early-bird-2023' ||
user.subscription.offer === 'renewal-early-bird-2024'
) { ) {
<ng-container i18n>Renew Plan</ng-container> <ng-container i18n>Renew Plan</ng-container>
} }

View File

@ -222,7 +222,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
private openAccountDetailDialog(aAccountId: string) { private openAccountDetailDialog(aAccountId: string) {
const dialogRef = this.dialog.open(AccountDetailDialog, { const dialogRef = this.dialog.open(AccountDetailDialog, {
autoFocus: false, autoFocus: false,
data: <AccountDetailDialogParams>{ data: {
accountId: aAccountId, accountId: aAccountId,
deviceType: this.deviceType, deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
@ -230,7 +230,7 @@ export class AccountsPageComponent implements OnDestroy, OnInit {
!this.hasImpersonationId && !this.hasImpersonationId &&
hasPermission(this.user?.permissions, permissions.createOrder) && hasPermission(this.user?.permissions, permissions.createOrder) &&
!this.user?.settings?.isRestrictedView !this.user?.settings?.isRestrictedView
}, } as AccountDetailDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -218,10 +218,10 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
public onImport() { public onImport() {
const dialogRef = this.dialog.open(ImportActivitiesDialog, { const dialogRef = this.dialog.open(ImportActivitiesDialog, {
data: <ImportActivitiesDialogParams>{ data: {
deviceType: this.deviceType, deviceType: this.deviceType,
user: this.user user: this.user
}, } as ImportActivitiesDialogParams,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });
@ -235,11 +235,11 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
public onImportDividends() { public onImportDividends() {
const dialogRef = this.dialog.open(ImportActivitiesDialog, { const dialogRef = this.dialog.open(ImportActivitiesDialog, {
data: <ImportActivitiesDialogParams>{ data: {
activityTypes: ['DIVIDEND'], activityTypes: ['DIVIDEND'],
deviceType: this.deviceType, deviceType: this.deviceType,
user: this.user user: this.user
}, } as ImportActivitiesDialogParams,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -505,7 +505,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
private openAccountDetailDialog(aAccountId: string) { private openAccountDetailDialog(aAccountId: string) {
const dialogRef = this.dialog.open(AccountDetailDialog, { const dialogRef = this.dialog.open(AccountDetailDialog, {
autoFocus: false, autoFocus: false,
data: <AccountDetailDialogParams>{ data: {
accountId: aAccountId, accountId: aAccountId,
deviceType: this.deviceType, deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
@ -513,7 +513,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
!this.hasImpersonationId && !this.hasImpersonationId &&
hasPermission(this.user?.permissions, permissions.createOrder) && hasPermission(this.user?.permissions, permissions.createOrder) &&
!this.user?.settings?.isRestrictedView !this.user?.settings?.isRestrictedView
}, } as AccountDetailDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -5,13 +5,14 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { MatPaginator, PageEvent } from '@angular/material/paginator'; import { MatPaginator, PageEvent } from '@angular/material/paginator';
import { import {
HistoricalDataItem, HistoricalDataItem,
InvestmentItem,
PortfolioInvestments, PortfolioInvestments,
PortfolioPerformance, PortfolioPerformance,
PortfolioPosition, PortfolioPosition,
ToggleOption,
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { GroupBy } from '@ghostfolio/common/types';
import { GroupBy, ToggleOption } from '@ghostfolio/common/types';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';

View File

@ -278,7 +278,8 @@
<ng-container i18n>Upgrade Plan</ng-container> <ng-container i18n>Upgrade Plan</ng-container>
} @else if ( } @else if (
user.subscription.offer === 'renewal' || user.subscription.offer === 'renewal' ||
user.subscription.offer === 'renewal-early-bird' user.subscription.offer === 'renewal-early-bird-2023' ||
user.subscription.offer === 'renewal-early-bird-2024'
) { ) {
<ng-container i18n>Renew Plan</ng-container> <ng-container i18n>Renew Plan</ng-container>
} }

View File

@ -230,8 +230,8 @@ export class DataService {
public fetchActivity(aActivityId: string) { public fetchActivity(aActivityId: string) {
return this.http.get<Activity>(`/api/v1/order/${aActivityId}`).pipe( return this.http.get<Activity>(`/api/v1/order/${aActivityId}`).pipe(
map((activity) => { map((activity) => {
activity.createdAt = parseISO(<string>(<unknown>activity.createdAt)); activity.createdAt = parseISO(activity.createdAt as unknown as string);
activity.date = parseISO(<string>(<unknown>activity.date)); activity.date = parseISO(activity.date as unknown as string);
return activity; return activity;
}) })
@ -387,8 +387,8 @@ export class DataService {
map((data) => { map((data) => {
if (data.orders) { if (data.orders) {
for (const order of data.orders) { for (const order of data.orders) {
order.createdAt = parseISO(<string>(<unknown>order.createdAt)); order.createdAt = parseISO(order.createdAt as unknown as string);
order.date = parseISO(<string>(<unknown>order.date)); order.date = parseISO(order.date as unknown as string);
} }
} }
@ -399,9 +399,9 @@ export class DataService {
public fetchInfo(): InfoItem { public fetchInfo(): InfoItem {
const info = cloneDeep((window as any).info); const info = cloneDeep((window as any).info);
const utmSource = <'ios' | 'trusted-web-activity'>( const utmSource = window.localStorage.getItem('utm_source') as
window.localStorage.getItem('utm_source') | 'ios'
); | 'trusted-web-activity';
info.globalPermissions = filterGlobalPermissions( info.globalPermissions = filterGlobalPermissions(
info.globalPermissions, info.globalPermissions,
@ -715,9 +715,9 @@ export class DataService {
public updateInfo() { public updateInfo() {
this.http.get<InfoItem>('/api/v1/info').subscribe((info) => { this.http.get<InfoItem>('/api/v1/info').subscribe((info) => {
const utmSource = <'ios' | 'trusted-web-activity'>( const utmSource = window.localStorage.getItem('utm_source') as
window.localStorage.getItem('utm_source') | 'ios'
); | 'trusted-web-activity';
info.globalPermissions = filterGlobalPermissions( info.globalPermissions = filterGlobalPermissions(
info.globalPermissions, info.globalPermissions,

View File

@ -104,9 +104,9 @@ export class UserService extends ObservableStore<UserStoreState> {
) { ) {
const dialogRef = this.dialog.open(SubscriptionInterstitialDialog, { const dialogRef = this.dialog.open(SubscriptionInterstitialDialog, {
autoFocus: false, autoFocus: false,
data: <SubscriptionInterstitialDialogParams>{ data: {
user user
}, } as SubscriptionInterstitialDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : '80vh', height: this.deviceType === 'mobile' ? '98vh' : '80vh',
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -8,7 +8,7 @@ export async function validateObjectForForm<T>({
ignoreFields = [], ignoreFields = [],
object object
}: { }: {
classDto: { new (): T }; classDto: new () => T;
form: FormGroup; form: FormGroup;
ignoreFields?: string[]; ignoreFields?: string[];
object: T; object: T;

View File

@ -12,9 +12,9 @@ import { environment } from './environments/environment';
(async () => { (async () => {
const response = await fetch('/api/v1/info'); const response = await fetch('/api/v1/info');
const info: InfoItem = await response.json(); const info: InfoItem = await response.json();
const utmSource = <'ios' | 'trusted-web-activity'>( const utmSource = window.localStorage.getItem('utm_source') as
window.localStorage.getItem('utm_source') | 'ios'
); | 'trusted-web-activity';
info.globalPermissions = filterGlobalPermissions( info.globalPermissions = filterGlobalPermissions(
info.globalPermissions, info.globalPermissions,

View File

@ -125,14 +125,14 @@ export const PROPERTY_SLACK_COMMUNITY_USERS = 'SLACK_COMMUNITY_USERS';
export const PROPERTY_STRIPE_CONFIG = 'STRIPE_CONFIG'; export const PROPERTY_STRIPE_CONFIG = 'STRIPE_CONFIG';
export const PROPERTY_SYSTEM_MESSAGE = 'SYSTEM_MESSAGE'; export const PROPERTY_SYSTEM_MESSAGE = 'SYSTEM_MESSAGE';
export const QUEUE_JOB_STATUS_LIST = <JobStatus[]>[ export const QUEUE_JOB_STATUS_LIST = [
'active', 'active',
'completed', 'completed',
'delayed', 'delayed',
'failed', 'failed',
'paused', 'paused',
'waiting' 'waiting'
]; ] as JobStatus[];
export const REPLACE_NAME_PARTS = [ export const REPLACE_NAME_PARTS = [
'Amundi Index Solutions -', 'Amundi Index Solutions -',

View File

@ -108,7 +108,7 @@ export function downloadAsFile({
content = JSON.stringify(content, undefined, ' '); content = JSON.stringify(content, undefined, ' ');
} }
const file = new Blob([<string>content], { const file = new Blob([content as string], {
type: contentType type: contentType
}); });
a.href = URL.createObjectURL(file); a.href = URL.createObjectURL(file);

View File

@ -50,8 +50,10 @@ import type { Subscription } from './subscription.interface';
import type { SymbolMetrics } from './symbol-metrics.interface'; import type { SymbolMetrics } from './symbol-metrics.interface';
import type { SystemMessage } from './system-message.interface'; import type { SystemMessage } from './system-message.interface';
import type { TabConfiguration } from './tab-configuration.interface'; import type { TabConfiguration } from './tab-configuration.interface';
import type { ToggleOption } from './toggle-option.interface';
import type { UserSettings } from './user-settings.interface'; import type { UserSettings } from './user-settings.interface';
import type { User } from './user.interface'; import type { User } from './user.interface';
import type { XRayRulesSettings } from './x-ray-rules-settings.interface';
export { export {
Access, Access,
@ -104,6 +106,8 @@ export {
Subscription, Subscription,
SymbolMetrics, SymbolMetrics,
TabConfiguration, TabConfiguration,
ToggleOption,
User, User,
UserSettings UserSettings,
XRayRulesSettings
}; };

View File

@ -1,4 +1,4 @@
export type ToggleOption = { export interface ToggleOption {
label: string; label: string;
value: string; value: string;
}; }

View File

@ -1,9 +1,9 @@
import { XRayRulesSettings } from '@ghostfolio/common/interfaces/x-ray-rules-settings.interface';
import { import {
ColorScheme, ColorScheme,
DateRange, DateRange,
HoldingsViewMode, HoldingsViewMode,
ViewMode, ViewMode
XRayRulesSettings
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
export interface UserSettings { export interface UserSettings {

View File

@ -1,4 +1,4 @@
export type XRayRulesSettings = { export interface XRayRulesSettings {
AccountClusterRiskCurrentInvestment?: RuleSettings; AccountClusterRiskCurrentInvestment?: RuleSettings;
AccountClusterRiskSingleAccount?: RuleSettings; AccountClusterRiskSingleAccount?: RuleSettings;
AllocationClusterRiskDevelopedMarkets?: RuleSettings; AllocationClusterRiskDevelopedMarkets?: RuleSettings;
@ -7,7 +7,7 @@ export type XRayRulesSettings = {
CurrencyClusterRiskCurrentInvestment?: RuleSettings; CurrencyClusterRiskCurrentInvestment?: RuleSettings;
EmergencyFundSetup?: RuleSettings; EmergencyFundSetup?: RuleSettings;
FeeRatioInitialInvestment?: RuleSettings; FeeRatioInitialInvestment?: RuleSettings;
}; }
interface RuleSettings { interface RuleSettings {
isActive: boolean; isActive: boolean;

View File

@ -16,10 +16,8 @@ import type { Market } from './market.type';
import type { OrderWithAccount } from './order-with-account.type'; import type { OrderWithAccount } from './order-with-account.type';
import type { RequestWithUser } from './request-with-user.type'; import type { RequestWithUser } from './request-with-user.type';
import type { SubscriptionOffer } from './subscription-offer.type'; import type { SubscriptionOffer } from './subscription-offer.type';
import type { ToggleOption } from './toggle-option.type';
import type { UserWithSettings } from './user-with-settings.type'; import type { UserWithSettings } from './user-with-settings.type';
import type { ViewMode } from './view-mode.type'; import type { ViewMode } from './view-mode.type';
import type { XRayRulesSettings } from './x-ray-rules-settings.type';
export type { export type {
AccessType, AccessType,
@ -40,8 +38,6 @@ export type {
OrderWithAccount, OrderWithAccount,
RequestWithUser, RequestWithUser,
SubscriptionOffer, SubscriptionOffer,
ToggleOption,
UserWithSettings, UserWithSettings,
ViewMode, ViewMode
XRayRulesSettings
}; };

View File

@ -1 +1,5 @@
export type SubscriptionOffer = 'default' | 'renewal' | 'renewal-early-bird'; export type SubscriptionOffer =
| 'default'
| 'renewal'
| 'renewal-early-bird-2023'
| 'renewal-early-bird-2024';

View File

@ -74,9 +74,9 @@ export class GfAccountBalancesComponent
date: new FormControl(new Date(), Validators.required) date: new FormControl(new Date(), Validators.required)
}); });
public dataSource: MatTableDataSource< public dataSource = new MatTableDataSource<
AccountBalancesResponse['balances'][0] AccountBalancesResponse['balances'][0]
> = new MatTableDataSource(); >();
public displayedColumns: string[] = ['date', 'value', 'actions']; public displayedColumns: string[] = ['date', 'value', 'actions'];
public Validators = Validators; public Validators = Validators;

View File

@ -157,7 +157,7 @@ export class GfActivitiesFilterComponent implements OnChanges, OnDestroy {
for (const type of Object.keys(filterGroupsMap)) { for (const type of Object.keys(filterGroupsMap)) {
filterGroups.push({ filterGroups.push({
name: <Filter['type']>translate(type), name: translate(type) as Filter['type'],
filters: filterGroupsMap[type] filters: filterGroupsMap[type]
}); });
} }

View File

@ -180,10 +180,10 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
debounceTime(300), debounceTime(300),
distinctUntilChanged(), distinctUntilChanged(),
mergeMap(async (searchTerm) => { mergeMap(async (searchTerm) => {
const result = <ISearchResults>{ const result = {
assetProfiles: [], assetProfiles: [],
holdings: [] holdings: []
}; } as ISearchResults;
try { try {
if (searchTerm) { if (searchTerm) {

View File

@ -109,13 +109,13 @@ export class GfBenchmarkComponent implements OnChanges, OnDestroy {
symbol symbol
}: AssetProfileIdentifier) { }: AssetProfileIdentifier) {
const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, { const dialogRef = this.dialog.open(GfBenchmarkDetailDialogComponent, {
data: <BenchmarkDetailDialogParams>{ data: {
dataSource, dataSource,
symbol, symbol,
colorScheme: this.user?.settings?.colorScheme, colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
locale: this.locale locale: this.locale
}, } as BenchmarkDetailDialogParams,
height: this.deviceType === 'mobile' ? '98vh' : undefined, height: this.deviceType === 'mobile' ? '98vh' : undefined,
width: this.deviceType === 'mobile' ? '100vw' : '50rem' width: this.deviceType === 'mobile' ? '100vw' : '50rem'
}); });

View File

@ -67,8 +67,7 @@ export class GfHoldingsTableComponent implements OnChanges, OnDestroy {
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<PortfolioPosition> = public dataSource = new MatTableDataSource<PortfolioPosition>();
new MatTableDataSource();
public displayedColumns = []; public displayedColumns = [];
public ignoreAssetSubClasses = [AssetSubClass.CASH]; public ignoreAssetSubClasses = [AssetSubClass.CASH];
public isLoading = true; public isLoading = true;

View File

@ -172,15 +172,14 @@ export class GfLineChartComponent
if (this.chartCanvas) { if (this.chartCanvas) {
if (this.chart) { if (this.chart) {
this.chart.data = data; this.chart.data = data;
this.chart.options.plugins.tooltip = <unknown>( this.chart.options.plugins.tooltip =
this.getTooltipPluginConfiguration() this.getTooltipPluginConfiguration() as unknown;
);
this.chart.options.animation = this.chart.options.animation =
this.isAnimated && this.isAnimated &&
<unknown>{ ({
x: this.getAnimationConfigurationForAxis({ labels, axis: 'x' }), x: this.getAnimationConfigurationForAxis({ labels, axis: 'x' }),
y: this.getAnimationConfigurationForAxis({ labels, axis: 'y' }) y: this.getAnimationConfigurationForAxis({ labels, axis: 'y' })
}; } as unknown);
this.chart.update(); this.chart.update();
} else { } else {
this.chart = new Chart(this.chartCanvas.nativeElement, { this.chart = new Chart(this.chartCanvas.nativeElement, {
@ -188,10 +187,10 @@ export class GfLineChartComponent
options: { options: {
animation: animation:
this.isAnimated && this.isAnimated &&
<unknown>{ ({
x: this.getAnimationConfigurationForAxis({ labels, axis: 'x' }), x: this.getAnimationConfigurationForAxis({ labels, axis: 'x' }),
y: this.getAnimationConfigurationForAxis({ labels, axis: 'y' }) y: this.getAnimationConfigurationForAxis({ labels, axis: 'y' })
}, } as unknown),
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
elements: { elements: {
point: { point: {
@ -200,7 +199,7 @@ export class GfLineChartComponent
} }
}, },
interaction: { intersect: false, mode: 'index' }, interaction: { intersect: false, mode: 'index' },
plugins: <unknown>{ plugins: {
legend: { legend: {
align: 'start', align: 'start',
display: this.showLegend, display: this.showLegend,
@ -210,7 +209,7 @@ export class GfLineChartComponent
verticalHoverLine: { verticalHoverLine: {
color: `rgba(${getTextColor(this.colorScheme)}, 0.1)` color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
} }
}, } as unknown,
scales: { scales: {
x: { x: {
border: { border: {
@ -325,7 +324,7 @@ export class GfLineChartComponent
unit: this.unit unit: this.unit
}), }),
mode: 'index', mode: 'index',
position: <unknown>'top', position: 'top' as unknown,
xAlign: 'center', xAlign: 'center',
yAlign: 'bottom' yAlign: 'bottom'
}; };

View File

@ -304,14 +304,14 @@ export class GfPortfolioProportionChartComponent
if (this.chartCanvas) { if (this.chartCanvas) {
if (this.chart) { if (this.chart) {
this.chart.data = data; this.chart.data = data;
this.chart.options.plugins.tooltip = <unknown>( this.chart.options.plugins.tooltip = this.getTooltipPluginConfiguration(
this.getTooltipPluginConfiguration(data) data
); ) as unknown;
this.chart.update(); this.chart.update();
} else { } else {
this.chart = new Chart(this.chartCanvas.nativeElement, { this.chart = new Chart(this.chartCanvas.nativeElement, {
data, data,
options: <unknown>{ options: {
animation: false, animation: false,
cutout: '70%', cutout: '70%',
layout: { layout: {
@ -358,7 +358,7 @@ export class GfPortfolioProportionChartComponent
legend: { display: false }, legend: { display: false },
tooltip: this.getTooltipPluginConfiguration(data) tooltip: this.getTooltipPluginConfiguration(data)
} }
}, } as unknown,
plugins: [ChartDataLabels], plugins: [ChartDataLabels],
type: 'doughnut' type: 'doughnut'
}); });
@ -405,7 +405,7 @@ export class GfPortfolioProportionChartComponent
symbol = $localize`No data available`; symbol = $localize`No data available`;
} }
const name = translate(this.positions[<string>symbol]?.name); const name = translate(this.positions[symbol as string]?.name);
let sum = 0; let sum = 0;
for (const item of context.dataset.data) { for (const item of context.dataset.data) {
@ -414,12 +414,12 @@ export class GfPortfolioProportionChartComponent
const percentage = (context.parsed * 100) / sum; const percentage = (context.parsed * 100) / sum;
if (<number>context.raw === Number.MAX_SAFE_INTEGER) { if ((context.raw as number) === Number.MAX_SAFE_INTEGER) {
return $localize`No data available`; return $localize`No data available`;
} else if (this.isInPercent) { } else if (this.isInPercent) {
return [`${name ?? symbol}`, `${percentage.toFixed(2)}%`]; return [`${name ?? symbol}`, `${percentage.toFixed(2)}%`];
} else { } else {
const value = <number>context.raw; const value = context.raw as number;
return [ return [
`${name ?? symbol}`, `${name ?? symbol}`,
`${value.toLocaleString(this.locale, { `${value.toLocaleString(this.locale, {

View File

@ -112,7 +112,7 @@ export abstract class AbstractMatFormField<T>
public _disabled: boolean = false; public _disabled: boolean = false;
public get disabled() { public get disabled() {
if (this.ngControl && this.ngControl.disabled !== null) { if (this.ngControl?.disabled !== null) {
return this.ngControl.disabled; return this.ngControl.disabled;
} }

View File

@ -46,7 +46,7 @@ export class GfTopHoldingsComponent implements OnChanges, OnDestroy {
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
public dataSource: MatTableDataSource<Holding> = new MatTableDataSource(); public dataSource = new MatTableDataSource<Holding>();
public displayedColumns: string[] = [ public displayedColumns: string[] = [
'name', 'name',
'valueInBaseCurrency', 'valueInBaseCurrency',

View File

@ -48,7 +48,7 @@ export class GfValueComponent implements OnChanges {
if (isNumber(this.value)) { if (isNumber(this.value)) {
this.isNumber = true; this.isNumber = true;
this.isString = false; this.isString = false;
this.absoluteValue = Math.abs(<number>this.value); this.absoluteValue = Math.abs(this.value as number);
if (this.colorizeSign) { if (this.colorizeSign) {
if (this.isCurrency) { if (this.isCurrency) {
@ -113,7 +113,7 @@ export class GfValueComponent implements OnChanges {
this.isString = true; this.isString = true;
if (this.isDate) { if (this.isDate) {
this.formattedValue = new Date(<string>this.value).toLocaleDateString( this.formattedValue = new Date(this.value).toLocaleDateString(
this.locale, this.locale,
{ {
day: '2-digit', day: '2-digit',

68
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.117.0", "version": "2.118.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.117.0", "version": "2.118.0",
"hasInstallScript": true, "hasInstallScript": true,
"license": "AGPL-3.0", "license": "AGPL-3.0",
"dependencies": { "dependencies": {
@ -40,7 +40,7 @@
"@nestjs/platform-express": "10.1.3", "@nestjs/platform-express": "10.1.3",
"@nestjs/schedule": "3.0.2", "@nestjs/schedule": "3.0.2",
"@nestjs/serve-static": "4.0.0", "@nestjs/serve-static": "4.0.0",
"@prisma/client": "5.20.0", "@prisma/client": "5.21.1",
"@simplewebauthn/browser": "9.0.1", "@simplewebauthn/browser": "9.0.1",
"@simplewebauthn/server": "9.0.3", "@simplewebauthn/server": "9.0.3",
"@stripe/stripe-js": "3.5.0", "@stripe/stripe-js": "3.5.0",
@ -150,7 +150,7 @@
"nx": "20.0.3", "nx": "20.0.3",
"prettier": "3.3.3", "prettier": "3.3.3",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "5.20.0", "prisma": "5.21.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"replace-in-file": "7.0.1", "replace-in-file": "7.0.1",
@ -8753,9 +8753,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@prisma/client": { "node_modules/@prisma/client": {
"version": "5.20.0", "version": "5.21.1",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.20.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.21.1.tgz",
"integrity": "sha512-CLv55ZuMuUawMsxoqxGtLT3bEZoa2W8L3Qnp6rDIFWy+ZBrUcOFKdoeGPSnbBqxc3SkdxJrF+D1veN/WNynZYA==", "integrity": "sha512-3n+GgbAZYjaS/k0M03yQsQfR1APbr411r74foknnsGpmhNKBG49VuUkxIU6jORgvJPChoD4WC4PqoHImN1FP0w==",
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
@ -8771,53 +8771,53 @@
} }
}, },
"node_modules/@prisma/debug": { "node_modules/@prisma/debug": {
"version": "5.20.0", "version": "5.21.1",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.20.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.21.1.tgz",
"integrity": "sha512-oCx79MJ4HSujokA8S1g0xgZUGybD4SyIOydoHMngFYiwEwYDQ5tBQkK5XoEHuwOYDKUOKRn/J0MEymckc4IgsQ==", "integrity": "sha512-uY8SAhcnORhvgtOrNdvWS98Aq/nkQ9QDUxrWAgW8XrCZaI3j2X7zb7Xe6GQSh6xSesKffFbFlkw0c2luHQviZA==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/engines": { "node_modules/@prisma/engines": {
"version": "5.20.0", "version": "5.21.1",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.20.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.21.1.tgz",
"integrity": "sha512-DtqkP+hcZvPEbj8t8dK5df2b7d3B8GNauKqaddRRqQBBlgkbdhJkxhoJTrOowlS3vaRt2iMCkU0+CSNn0KhqAQ==", "integrity": "sha512-hGVTldUkIkTwoV8//hmnAAiAchi4oMEKD3aW5H2RrnI50tTdwza7VQbTTAyN3OIHWlK5DVg6xV7X8N/9dtOydA==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "5.20.0", "@prisma/debug": "5.21.1",
"@prisma/engines-version": "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284", "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36",
"@prisma/fetch-engine": "5.20.0", "@prisma/fetch-engine": "5.21.1",
"@prisma/get-platform": "5.20.0" "@prisma/get-platform": "5.21.1"
} }
}, },
"node_modules/@prisma/engines-version": { "node_modules/@prisma/engines-version": {
"version": "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284", "version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284.tgz", "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36.tgz",
"integrity": "sha512-Lg8AS5lpi0auZe2Mn4gjuCg081UZf88k3cn0RCwHgR+6cyHHpttPZBElJTHf83ZGsRNAmVCZCfUGA57WB4u4JA==", "integrity": "sha512-qvnEflL0//lh44S/T9NcvTMxfyowNeUxTunPcDfKPjyJNrCNf2F1zQLcUv5UHAruECpX+zz21CzsC7V2xAeM7Q==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@prisma/fetch-engine": { "node_modules/@prisma/fetch-engine": {
"version": "5.20.0", "version": "5.21.1",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.20.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.21.1.tgz",
"integrity": "sha512-JVcaPXC940wOGpCOwuqQRTz6I9SaBK0c1BAyC1pcz9xBi+dzFgUu3G/p9GV1FhFs9OKpfSpIhQfUJE9y00zhqw==", "integrity": "sha512-70S31vgpCGcp9J+mh/wHtLCkVezLUqe/fGWk3J3JWZIN7prdYSlr1C0niaWUyNK2VflLXYi8kMjAmSxUVq6WGQ==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "5.20.0", "@prisma/debug": "5.21.1",
"@prisma/engines-version": "5.20.0-12.06fc58a368dc7be9fbbbe894adf8d445d208c284", "@prisma/engines-version": "5.21.1-1.bf0e5e8a04cada8225617067eaa03d041e2bba36",
"@prisma/get-platform": "5.20.0" "@prisma/get-platform": "5.21.1"
} }
}, },
"node_modules/@prisma/get-platform": { "node_modules/@prisma/get-platform": {
"version": "5.20.0", "version": "5.21.1",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.20.0.tgz", "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.21.1.tgz",
"integrity": "sha512-8/+CehTZZNzJlvuryRgc77hZCWrUDYd/PmlZ7p2yNXtmf2Una4BWnTbak3us6WVdqoz5wmptk6IhsXdG2v5fmA==", "integrity": "sha512-sRxjL3Igst3ct+e8ya/x//cDXmpLbZQ5vfps2N4tWl4VGKQAmym77C/IG/psSMsQKszc8uFC/q1dgmKFLUgXZQ==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/debug": "5.20.0" "@prisma/debug": "5.21.1"
} }
}, },
"node_modules/@redis/bloom": { "node_modules/@redis/bloom": {
@ -29794,14 +29794,14 @@
} }
}, },
"node_modules/prisma": { "node_modules/prisma": {
"version": "5.20.0", "version": "5.21.1",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.20.0.tgz", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.21.1.tgz",
"integrity": "sha512-6obb3ucKgAnsGS9x9gLOe8qa51XxvJ3vLQtmyf52CTey1Qcez3A6W6ROH5HIz5Q5bW+0VpmZb8WBohieMFGpig==", "integrity": "sha512-PB+Iqzld/uQBPaaw2UVIk84kb0ITsLajzsxzsadxxl54eaU5Gyl2/L02ysivHxK89t7YrfQJm+Ggk37uvM70oQ==",
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@prisma/engines": "5.20.0" "@prisma/engines": "5.21.1"
}, },
"bin": { "bin": {
"prisma": "build/index.js" "prisma": "build/index.js"

View File

@ -1,6 +1,6 @@
{ {
"name": "ghostfolio", "name": "ghostfolio",
"version": "2.118.0", "version": "2.119.0",
"homepage": "https://ghostfol.io", "homepage": "https://ghostfol.io",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio", "repository": "https://github.com/ghostfolio/ghostfolio",
@ -86,7 +86,7 @@
"@nestjs/platform-express": "10.1.3", "@nestjs/platform-express": "10.1.3",
"@nestjs/schedule": "3.0.2", "@nestjs/schedule": "3.0.2",
"@nestjs/serve-static": "4.0.0", "@nestjs/serve-static": "4.0.0",
"@prisma/client": "5.20.0", "@prisma/client": "5.21.1",
"@simplewebauthn/browser": "9.0.1", "@simplewebauthn/browser": "9.0.1",
"@simplewebauthn/server": "9.0.3", "@simplewebauthn/server": "9.0.3",
"@stripe/stripe-js": "3.5.0", "@stripe/stripe-js": "3.5.0",
@ -196,7 +196,7 @@
"nx": "20.0.3", "nx": "20.0.3",
"prettier": "3.3.3", "prettier": "3.3.3",
"prettier-plugin-organize-attributes": "1.0.0", "prettier-plugin-organize-attributes": "1.0.0",
"prisma": "5.20.0", "prisma": "5.21.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"replace-in-file": "7.0.1", "replace-in-file": "7.0.1",