Browse Source

Merge remote-tracking branch 'origin/main' into feature/extend-holdings-endpoint-for-cash

pull/5650/head
KenTandrian 3 weeks ago
parent
commit
6d522c2a07
  1. 8
      CHANGELOG.md
  2. 3
      apps/api/src/app/access/access.controller.ts
  3. 2
      apps/api/src/app/account-balance/account-balance.controller.ts
  4. 3
      apps/api/src/app/account-balance/account-balance.service.ts
  5. 8
      apps/api/src/app/account/account.controller.ts
  6. 8
      apps/api/src/app/admin/admin.controller.ts
  7. 10
      apps/api/src/app/auth/auth.controller.ts
  8. 2
      apps/api/src/app/auth/interfaces/interfaces.ts
  9. 11
      apps/api/src/app/auth/web-auth.service.ts
  10. 3
      apps/api/src/app/endpoints/market-data/market-data.controller.ts
  11. 24
      apps/api/src/app/endpoints/market-data/update-bulk-market-data.dto.ts
  12. 4
      apps/api/src/app/endpoints/sitemap/sitemap.service.ts
  13. 4
      apps/api/src/app/endpoints/tags/tags.controller.ts
  14. 2
      apps/api/src/app/endpoints/watchlist/watchlist.controller.ts
  15. 11
      apps/api/src/app/import/import-data.dto.ts
  16. 8
      apps/api/src/app/import/import.service.ts
  17. 3
      apps/api/src/app/order/order.controller.ts
  18. 10
      apps/api/src/app/order/order.service.ts
  19. 3
      apps/api/src/app/platform/platform.controller.ts
  20. 2
      apps/api/src/app/portfolio/rules.service.ts
  21. 10
      apps/api/src/app/user/user.controller.ts
  22. 11
      apps/api/src/events/asset-profile-changed.event.ts
  23. 61
      apps/api/src/events/asset-profile-changed.listener.ts
  24. 17
      apps/api/src/events/events.module.ts
  25. 4
      apps/api/src/middlewares/html-template.middleware.ts
  26. 3
      apps/api/src/models/interfaces/rule.interface.ts
  27. 2
      apps/api/src/models/rule.ts
  28. 7
      apps/api/src/models/rules/account-cluster-risk/current-investment.ts
  29. 7
      apps/api/src/models/rules/account-cluster-risk/single-account.ts
  30. 7
      apps/api/src/models/rules/asset-class-cluster-risk/equity.ts
  31. 7
      apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts
  32. 7
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  33. 7
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  34. 3
      apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts
  35. 3
      apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts
  36. 3
      apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts
  37. 3
      apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
  38. 3
      apps/api/src/models/rules/liquidity/buying-power.ts
  39. 2
      apps/api/src/models/rules/regional-market-cluster-risk/interfaces/rule-settings.interface.ts
  40. 1
      apps/api/src/services/configuration/configuration.service.ts
  41. 7
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  42. 1
      apps/api/src/services/interfaces/environment.interface.ts
  43. 2
      apps/api/src/services/market-data/market-data.service.ts
  44. 3
      apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts
  45. 2
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  46. 3
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts
  47. 4
      apps/client/src/app/components/admin-platform/admin-platform.component.ts
  48. 4
      apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts
  49. 4
      apps/client/src/app/components/admin-tag/admin-tag.component.ts
  50. 4
      apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts
  51. 3
      apps/client/src/app/components/header/header.component.ts
  52. 3
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts
  53. 5
      apps/client/src/app/components/rule/rule.component.ts
  54. 3
      apps/client/src/app/components/rules/rules.component.ts
  55. 4
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  56. 3
      apps/client/src/app/components/user-account-access/user-account-access.component.ts
  57. 9
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  58. 4
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts
  59. 3
      apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts
  60. 18
      apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts
  61. 159
      apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html
  62. 26
      apps/client/src/app/pages/blog/blog-page.html
  63. 9
      apps/client/src/app/pages/blog/blog-page.routes.ts
  64. 4
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  65. 4
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  66. 11
      apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts
  67. 3
      apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts
  68. 11
      apps/client/src/app/services/admin.service.ts
  69. 39
      apps/client/src/app/services/data.service.ts
  70. 11
      apps/client/src/app/services/import-activities.service.ts
  71. 7
      apps/client/src/app/services/web-authn.service.ts
  72. BIN
      apps/client/src/assets/images/blog/black-weeks-2025.jpg
  73. 1
      apps/client/src/polyfills.ts
  74. 0
      libs/common/src/lib/dtos/auth-device.dto.ts
  75. 0
      libs/common/src/lib/dtos/create-access.dto.ts
  76. 0
      libs/common/src/lib/dtos/create-account-balance.dto.ts
  77. 3
      libs/common/src/lib/dtos/create-account-with-balances.dto.ts
  78. 2
      libs/common/src/lib/dtos/create-account.dto.ts
  79. 2
      libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts
  80. 2
      libs/common/src/lib/dtos/create-asset-profile.dto.ts
  81. 2
      libs/common/src/lib/dtos/create-order.dto.ts
  82. 0
      libs/common/src/lib/dtos/create-platform.dto.ts
  83. 0
      libs/common/src/lib/dtos/create-tag.dto.ts
  84. 0
      libs/common/src/lib/dtos/create-watchlist-item.dto.ts
  85. 0
      libs/common/src/lib/dtos/delete-own-user.dto.ts
  86. 51
      libs/common/src/lib/dtos/index.ts
  87. 0
      libs/common/src/lib/dtos/transfer-balance.dto.ts
  88. 0
      libs/common/src/lib/dtos/update-access.dto.ts
  89. 2
      libs/common/src/lib/dtos/update-account.dto.ts
  90. 2
      libs/common/src/lib/dtos/update-asset-profile.dto.ts
  91. 4
      libs/common/src/lib/dtos/update-bulk-market-data.dto.ts
  92. 0
      libs/common/src/lib/dtos/update-market-data.dto.ts
  93. 2
      libs/common/src/lib/dtos/update-order.dto.ts
  94. 0
      libs/common/src/lib/dtos/update-own-access-token.dto.ts
  95. 0
      libs/common/src/lib/dtos/update-platform.dto.ts
  96. 2
      libs/common/src/lib/dtos/update-property.dto.ts
  97. 0
      libs/common/src/lib/dtos/update-tag.dto.ts
  98. 2
      libs/common/src/lib/dtos/update-user-setting.dto.ts
  99. 16
      libs/common/src/lib/interfaces/index.ts
  100. 0
      libs/common/src/lib/interfaces/rule-settings.interface.ts

8
CHANGELOG.md

@ -5,7 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## 2.217.1 - 2025-11-16
### Added
- Introduced support for automatically gathering required exchange rates, exposed as an environment variable (`ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES`)
- Added a blog post: _Black Weeks 2025_
### Added
@ -16,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Refactored the get holding functionality in the portfolio service
- Changed the user data loading in the user detail dialog of the admin control panel’s users section to fetch data on demand
- Exposed the authentication with access token as an environment variable (`ENABLE_FEATURE_AUTH_TOKEN`)
- Improved the search functionality of the _Financial Modeling Prep_ service
- Improved the language localization for German (`de`)
- Upgraded `prisma` from version `6.18.0` to `6.19.0`

3
apps/api/src/app/access/access.controller.ts

@ -1,6 +1,7 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos';
import { Access } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
@ -23,8 +24,6 @@ import { Access as AccessModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccessService } from './access.service';
import { CreateAccessDto } from './create-access.dto';
import { UpdateAccessDto } from './update-access.dto';
@Controller('access')
export class AccessController {

2
apps/api/src/app/account-balance/account-balance.controller.ts

@ -1,6 +1,7 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
@ -20,7 +21,6 @@ import { AccountBalance } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccountBalanceService } from './account-balance.service';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
@Controller('account-balance')
export class AccountBalanceController {

3
apps/api/src/app/account-balance/account-balance.service.ts

@ -2,6 +2,7 @@ import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos';
import { DATE_FORMAT, getSum, resetHours } from '@ghostfolio/common/helper';
import {
AccountBalancesResponse,
@ -15,8 +16,6 @@ import { AccountBalance, Prisma } from '@prisma/client';
import { Big } from 'big.js';
import { format, parseISO } from 'date-fns';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
@Injectable()
export class AccountBalanceService {
public constructor(

8
apps/api/src/app/account/account.controller.ts

@ -7,6 +7,11 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import {
CreateAccountDto,
TransferBalanceDto,
UpdateAccountDto
} from '@ghostfolio/common/dtos';
import {
AccountBalancesResponse,
AccountResponse,
@ -36,9 +41,6 @@ import { Account as AccountModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccountService } from './account.service';
import { CreateAccountDto } from './create-account.dto';
import { TransferBalanceDto } from './transfer-balance.dto';
import { UpdateAccountDto } from './update-account.dto';
@Controller('account')
export class AccountController {

8
apps/api/src/app/admin/admin.controller.ts

@ -4,7 +4,6 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { DemoService } from '@ghostfolio/api/services/demo/demo.service';
import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import {
@ -13,6 +12,10 @@ import {
GATHER_ASSET_PROFILE_PROCESS_JOB_NAME,
GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS
} from '@ghostfolio/common/config';
import {
UpdateAssetProfileDto,
UpdatePropertyDto
} from '@ghostfolio/common/dtos';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import {
AdminData,
@ -52,7 +55,6 @@ import { isDate, parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AdminService } from './admin.service';
import { UpdateAssetProfileDto } from './update-asset-profile.dto';
@Controller('admin')
export class AdminController {
@ -305,7 +307,7 @@ export class AdminController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updateProperty(
@Param('key') key: string,
@Body() data: PropertyDto
@Body() data: UpdatePropertyDto
) {
return this.adminService.putSetting(key, data.value);
}

10
apps/api/src/app/auth/auth.controller.ts

@ -2,7 +2,11 @@ import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { OAuthResponse } from '@ghostfolio/common/interfaces';
import {
AssertionCredentialJSON,
AttestationCredentialJSON,
OAuthResponse
} from '@ghostfolio/common/interfaces';
import {
Body,
@ -22,10 +26,6 @@ import { Request, Response } from 'express';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import { AuthService } from './auth.service';
import {
AssertionCredentialJSON,
AttestationCredentialJSON
} from './interfaces/simplewebauthn';
@Controller('auth')
export class AuthController {

2
apps/api/src/app/auth/interfaces/interfaces.ts

@ -1,4 +1,4 @@
import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto';
import { AuthDeviceDto } from '@ghostfolio/common/dtos';
import { Provider } from '@prisma/client';

11
apps/api/src/app/auth/web-auth.service.ts

@ -1,7 +1,11 @@
import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto';
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { AuthDeviceDto } from '@ghostfolio/common/dtos';
import {
AssertionCredentialJSON,
AttestationCredentialJSON
} from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
@ -27,11 +31,6 @@ import {
import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers';
import ms from 'ms';
import {
AssertionCredentialJSON,
AttestationCredentialJSON
} from './interfaces/simplewebauthn';
@Injectable()
export class WebAuthService {
public constructor(

3
apps/api/src/app/endpoints/market-data/market-data.controller.ts

@ -10,6 +10,7 @@ import {
ghostfolioFearAndGreedIndexSymbolCryptocurrencies,
ghostfolioFearAndGreedIndexSymbolStocks
} from '@ghostfolio/common/config';
import { UpdateBulkMarketDataDto } from '@ghostfolio/common/dtos';
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
import {
MarketDataDetailsResponse,
@ -35,8 +36,6 @@ import { DataSource, Prisma } from '@prisma/client';
import { parseISO } from 'date-fns';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto';
@Controller('market-data')
export class MarketDataController {
public constructor(

24
apps/api/src/app/endpoints/market-data/update-bulk-market-data.dto.ts

@ -1,24 +0,0 @@
import { Type } from 'class-transformer';
import {
ArrayNotEmpty,
IsArray,
IsISO8601,
IsNumber,
IsOptional
} from 'class-validator';
export class UpdateBulkMarketDataDto {
@ArrayNotEmpty()
@IsArray()
@Type(() => UpdateMarketDataDto)
marketData: UpdateMarketDataDto[];
}
class UpdateMarketDataDto {
@IsISO8601()
@IsOptional()
date?: string;
@IsNumber()
marketPrice: number;
}

4
apps/api/src/app/endpoints/sitemap/sitemap.service.ts

@ -116,6 +116,10 @@ export class SitemapService {
{
languageCode: 'en',
routerLink: ['2025', '09', 'hacktoberfest-2025']
},
{
languageCode: 'en',
routerLink: ['2025', '11', 'black-weeks-2025']
}
]
.map(({ languageCode, routerLink }) => {

4
apps/api/src/app/endpoints/tags/tags.controller.ts

@ -1,6 +1,7 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
@ -21,9 +22,6 @@ import { AuthGuard } from '@nestjs/passport';
import { Tag } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateTagDto } from './create-tag.dto';
import { UpdateTagDto } from './update-tag.dto';
@Controller('tags')
export class TagsController {
public constructor(

2
apps/api/src/app/endpoints/watchlist/watchlist.controller.ts

@ -4,6 +4,7 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { CreateWatchlistItemDto } from '@ghostfolio/common/dtos';
import { WatchlistResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
@ -26,7 +27,6 @@ import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateWatchlistItemDto } from './create-watchlist-item.dto';
import { WatchlistService } from './watchlist.service';
@Controller('watchlist')

11
apps/api/src/app/import/import-data.dto.ts

@ -1,12 +1,13 @@
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import {
CreateAccountWithBalancesDto,
CreateAssetProfileWithMarketDataDto,
CreateOrderDto,
CreateTagDto
} from '@ghostfolio/common/dtos';
import { Type } from 'class-transformer';
import { IsArray, IsOptional, ValidateNested } from 'class-validator';
import { CreateTagDto } from '../endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto';
export class ImportDataDto {
@IsArray()
@IsOptional()

8
apps/api/src/app/import/import.service.ts

@ -1,6 +1,4 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
@ -11,6 +9,11 @@ import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathe
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config';
import {
CreateAssetProfileDto,
CreateAccountDto,
CreateOrderDto
} from '@ghostfolio/common/dtos';
import {
getAssetProfileIdentifier,
parseDate
@ -34,7 +37,6 @@ import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns';
import { omit, uniqBy } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { CreateAssetProfileDto } from '../admin/create-asset-profile.dto';
import { ImportDataDto } from './import-data.dto';
@Injectable()

3
apps/api/src/app/order/order.controller.ts

@ -11,6 +11,7 @@ import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
HEADER_KEY_IMPERSONATION
} from '@ghostfolio/common/config';
import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos';
import {
ActivitiesResponse,
ActivityResponse
@ -39,9 +40,7 @@ import { Order as OrderModel, Prisma } from '@prisma/client';
import { parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateOrderDto } from './create-order.dto';
import { OrderService } from './order.service';
import { UpdateOrderDto } from './update-order.dto';
@Controller('order')
export class OrderController {

10
apps/api/src/app/order/order.service.ts

@ -1,4 +1,5 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { AssetProfileChangedEvent } from '@ghostfolio/api/events/asset-profile-changed.event';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
@ -225,6 +226,15 @@ export class OrderService {
});
}
this.eventEmitter.emit(
AssetProfileChangedEvent.getName(),
new AssetProfileChangedEvent({
currency: order.SymbolProfile.currency,
dataSource: order.SymbolProfile.dataSource,
symbol: order.SymbolProfile.symbol
})
);
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({

3
apps/api/src/app/platform/platform.controller.ts

@ -1,5 +1,6 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { permissions } from '@ghostfolio/common/permissions';
import {
@ -17,9 +18,7 @@ import { AuthGuard } from '@nestjs/passport';
import { Platform } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreatePlatformDto } from './create-platform.dto';
import { PlatformService } from './platform.service';
import { UpdatePlatformDto } from './update-platform.dto';
@Controller('platform')
export class PlatformController {

2
apps/api/src/app/portfolio/rules.service.ts

@ -1,7 +1,7 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import {
PortfolioReportRule,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';

10
apps/api/src/app/user/user.controller.ts

@ -3,9 +3,15 @@ import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import {
DeleteOwnUserDto,
UpdateOwnAccessTokenDto,
UpdateUserSettingDto
} from '@ghostfolio/common/dtos';
import {
AccessTokenResponse,
User,
UserItem,
UserSettings
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -31,10 +37,6 @@ import { User as UserModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { merge, size } from 'lodash';
import { DeleteOwnUserDto } from './delete-own-user.dto';
import { UserItem } from './interfaces/user-item.interface';
import { UpdateOwnAccessTokenDto } from './update-own-access-token.dto';
import { UpdateUserSettingDto } from './update-user-setting.dto';
import { UserService } from './user.service';
@Controller('user')

11
apps/api/src/events/asset-profile-changed.event.ts

@ -0,0 +1,11 @@
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
export class AssetProfileChangedEvent {
public constructor(
public readonly data: AssetProfileIdentifier & { currency: string }
) {}
public static getName(): string {
return 'assetProfile.changed';
}
}

61
apps/api/src/events/asset-profile-changed.listener.ts

@ -0,0 +1,61 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { AssetProfileChangedEvent } from './asset-profile-changed.event';
@Injectable()
export class AssetProfileChangedListener {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly dataGatheringService: DataGatheringService,
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly orderService: OrderService
) {}
@OnEvent(AssetProfileChangedEvent.getName())
public async handleAssetProfileChanged(event: AssetProfileChangedEvent) {
Logger.log(
`Asset profile of ${event.data.symbol} (${event.data.dataSource}) has changed`,
'AssetProfileChangedListener'
);
if (
this.configurationService.get(
'ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES'
) === false ||
event.data.currency === DEFAULT_CURRENCY
) {
return;
}
const existingCurrencies = this.exchangeRateDataService.getCurrencies();
if (!existingCurrencies.includes(event.data.currency)) {
Logger.log(
`New currency ${event.data.currency} has been detected`,
'AssetProfileChangedListener'
);
await this.exchangeRateDataService.initialize();
}
const { dateOfFirstActivity } =
await this.orderService.getStatisticsByCurrency(event.data.currency);
if (dateOfFirstActivity) {
await this.dataGatheringService.gatherSymbol({
dataSource: this.dataProviderService.getDataSourceForExchangeRates(),
date: dateOfFirstActivity,
symbol: `${DEFAULT_CURRENCY}${event.data.currency}`
});
}
}
}

17
apps/api/src/events/events.module.ts

@ -1,11 +1,24 @@
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { Module } from '@nestjs/common';
import { AssetProfileChangedListener } from './asset-profile-changed.listener';
import { PortfolioChangedListener } from './portfolio-changed.listener';
@Module({
imports: [RedisCacheModule],
providers: [PortfolioChangedListener]
imports: [
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
ExchangeRateDataModule,
OrderModule,
RedisCacheModule
],
providers: [AssetProfileChangedListener, PortfolioChangedListener]
})
export class EventsModule {}

4
apps/api/src/middlewares/html-template.middleware.ts

@ -79,6 +79,10 @@ const locales = {
'/en/blog/2025/09/hacktoberfest-2025': {
featureGraphicPath: 'assets/images/blog/hacktoberfest-2025.png',
title: `Hacktoberfest 2025 - ${title}`
},
'/en/blog/2025/11/black-weeks-2025': {
featureGraphicPath: 'assets/images/blog/black-weeks-2025.jpg',
title: `Black Weeks 2025 - ${title}`
}
};

3
apps/api/src/models/interfaces/rule.interface.ts

@ -1,5 +1,4 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { EvaluationResult } from './evaluation-result.interface';

2
apps/api/src/models/rule.ts

@ -1,10 +1,10 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { groupBy } from '@ghostfolio/common/helper';
import {
PortfolioPosition,
PortfolioReportRule,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';

7
apps/api/src/models/rules/account-cluster-risk/current-investment.ts

@ -1,8 +1,11 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PortfolioDetails, UserSettings } from '@ghostfolio/common/interfaces';
import {
PortfolioDetails,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
import { Account } from '@prisma/client';

7
apps/api/src/models/rules/account-cluster-risk/single-account.ts

@ -1,8 +1,11 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PortfolioDetails, UserSettings } from '@ghostfolio/common/interfaces';
import {
PortfolioDetails,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
private accounts: PortfolioDetails['accounts'];

7
apps/api/src/models/rules/asset-class-cluster-risk/equity.ts

@ -1,8 +1,11 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
export class AssetClassClusterRiskEquity extends Rule<Settings> {
private holdings: PortfolioPosition[];

7
apps/api/src/models/rules/asset-class-cluster-risk/fixed-income.ts

@ -1,8 +1,11 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
export class AssetClassClusterRiskFixedIncome extends Rule<Settings> {
private holdings: PortfolioPosition[];

7
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -1,8 +1,11 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Settings> {
private holdings: PortfolioPosition[];

7
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -1,8 +1,11 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PortfolioPosition, UserSettings } from '@ghostfolio/common/interfaces';
import {
PortfolioPosition,
RuleSettings,
UserSettings
} from '@ghostfolio/common/interfaces';
export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
private holdings: PortfolioPosition[];

3
apps/api/src/models/rules/economic-market-cluster-risk/developed-markets.ts

@ -1,8 +1,7 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
export class EconomicMarketClusterRiskDevelopedMarkets extends Rule<Settings> {
private currentValueInBaseCurrency: number;

3
apps/api/src/models/rules/economic-market-cluster-risk/emerging-markets.ts

@ -1,8 +1,7 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
export class EconomicMarketClusterRiskEmergingMarkets extends Rule<Settings> {
private currentValueInBaseCurrency: number;

3
apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts

@ -1,8 +1,7 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
export class EmergencyFundSetup extends Rule<Settings> {
private emergencyFund: number;

3
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts

@ -1,8 +1,7 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
export class FeeRatioInitialInvestment extends Rule<Settings> {
private fees: number;

3
apps/api/src/models/rules/liquidity/buying-power.ts

@ -1,8 +1,7 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { Rule } from '@ghostfolio/api/models/rule';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { RuleSettings, UserSettings } from '@ghostfolio/common/interfaces';
export class BuyingPower extends Rule<Settings> {
private buyingPower: number;

2
apps/api/src/models/rules/regional-market-cluster-risk/interfaces/rule-settings.interface.ts

@ -1,4 +1,4 @@
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { RuleSettings } from '@ghostfolio/common/interfaces';
export interface Settings extends RuleSettings {
baseCurrency: string;

1
apps/api/src/services/configuration/configuration.service.ts

@ -43,6 +43,7 @@ export class ConfigurationService {
ENABLE_FEATURE_AUTH_GOOGLE: bool({ default: false }),
ENABLE_FEATURE_AUTH_TOKEN: bool({ default: true }),
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: bool({ default: true }),
ENABLE_FEATURE_READ_ONLY_MODE: bool({ default: false }),
ENABLE_FEATURE_STATISTICS: bool({ default: false }),
ENABLE_FEATURE_SUBSCRIPTION: bool({ default: false }),

7
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -504,8 +504,11 @@ export class FinancialModelingPrepService implements DataProviderInterface {
).then((res) => res.json());
items = result
.filter(({ symbol }) => {
if (includeIndices === false && symbol.startsWith('^')) {
.filter(({ exchange, symbol }) => {
if (
exchange === 'FOREX' ||
(includeIndices === false && symbol.startsWith('^'))
) {
return false;
}

1
apps/api/src/services/interfaces/environment.interface.ts

@ -19,6 +19,7 @@ export interface Environment extends CleanedEnvAccessors {
ENABLE_FEATURE_AUTH_GOOGLE: boolean;
ENABLE_FEATURE_AUTH_TOKEN: boolean;
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
ENABLE_FEATURE_GATHER_NEW_EXCHANGE_RATES: boolean;
ENABLE_FEATURE_READ_ONLY_MODE: boolean;
ENABLE_FEATURE_STATISTICS: boolean;
ENABLE_FEATURE_SUBSCRIPTION: boolean;

2
apps/api/src/services/market-data/market-data.service.ts

@ -1,7 +1,7 @@
import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto';
import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface';
import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { UpdateMarketDataDto } from '@ghostfolio/common/dtos';
import { resetHours } from '@ghostfolio/common/helper';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';

3
apps/client/src/app/components/account-detail-dialog/account-detail-dialog.component.ts

@ -1,11 +1,10 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto';
import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component';
import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component';
import { GfInvestmentChartComponent } from '@ghostfolio/client/components/investment-chart/investment-chart.component';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { NUMERICAL_PRECISION_THRESHOLD_6_FIGURES } from '@ghostfolio/common/config';
import { CreateAccountBalanceDto } from '@ghostfolio/common/dtos';
import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import {
AccountBalancesResponse,

2
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -1,4 +1,3 @@
import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
@ -15,6 +14,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfSymbolPipe } from '@ghostfolio/common/pipes';
import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter';
import { translate } from '@ghostfolio/ui/i18n';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';

3
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts

@ -1,5 +1,3 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { AdminMarketDataService } from '@ghostfolio/client/components/admin-market-data/admin-market-data.service';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { AdminService } from '@ghostfolio/client/services/admin.service';
@ -10,6 +8,7 @@ import {
ASSET_CLASS_MAPPING,
PROPERTY_IS_DATA_GATHERING_ENABLED
} from '@ghostfolio/common/config';
import { UpdateAssetProfileDto } from '@ghostfolio/common/dtos';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
AdminMarketDataDetails,

4
apps/client/src/app/components/admin-platform/admin-platform.component.ts

@ -1,11 +1,9 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto';
import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto';
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import {

4
apps/client/src/app/components/admin-platform/create-or-update-platform-dialog/create-or-update-platform-dialog.component.ts

@ -1,7 +1,5 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto';
import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import {

4
apps/client/src/app/components/admin-tag/admin-tag.component.ts

@ -1,10 +1,8 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto';
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos';
import {
ChangeDetectionStrategy,

4
apps/client/src/app/components/admin-tag/create-or-update-tag-dialog/create-or-update-tag-dialog.component.ts

@ -1,7 +1,5 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { CreateTagDto, UpdateTagDto } from '@ghostfolio/common/dtos';
import {
ChangeDetectionStrategy,

3
apps/client/src/app/components/header/header.component.ts

@ -1,5 +1,3 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { LoginWithAccessTokenDialogParams } from '@ghostfolio/client/components/login-with-access-token-dialog/interfaces/interfaces';
import { GfLoginWithAccessTokenDialogComponent } from '@ghostfolio/client/components/login-with-access-token-dialog/login-with-access-token-dialog.component';
import { LayoutService } from '@ghostfolio/client/core/layout.service';
@ -12,6 +10,7 @@ import {
} from '@ghostfolio/client/services/settings-storage.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { UpdateUserSettingDto } from '@ghostfolio/common/dtos';
import { Filter, InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes';

3
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.component.ts

@ -1,5 +1,3 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component';
import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component';
import { DataService } from '@ghostfolio/client/services/data.service';
@ -9,6 +7,7 @@ import {
NUMERICAL_PRECISION_THRESHOLD_5_FIGURES,
NUMERICAL_PRECISION_THRESHOLD_6_FIGURES
} from '@ghostfolio/common/config';
import { CreateOrderDto } from '@ghostfolio/common/dtos';
import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import {
Activity,

5
apps/client/src/app/components/rule/rule.component.ts

@ -1,8 +1,7 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { RuleSettings } from '@ghostfolio/api/models/interfaces/rule-settings.interface';
import { UpdateUserSettingDto } from '@ghostfolio/common/dtos';
import {
PortfolioReportRule,
RuleSettings,
XRayRulesSettings
} from '@ghostfolio/common/interfaces';

3
apps/client/src/app/components/rules/rules.component.ts

@ -1,6 +1,5 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { GfRuleComponent } from '@ghostfolio/client/components/rule/rule.component';
import { UpdateUserSettingDto } from '@ghostfolio/common/dtos';
import {
PortfolioReportRule,
XRayRulesSettings

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

@ -1,9 +1,7 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto';
import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos';
import {
ChangeDetectionStrategy,

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

@ -1,11 +1,10 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto';
import { GfAccessTableComponent } from '@ghostfolio/client/components/access-table/access-table.component';
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { CreateAccessDto } from '@ghostfolio/common/dtos';
import { Access, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';

9
apps/client/src/app/pages/accounts/accounts-page.component.ts

@ -1,13 +1,14 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
import { GfAccountDetailDialogComponent } from '@ghostfolio/client/components/account-detail-dialog/account-detail-dialog.component';
import { AccountDetailDialogParams } from '@ghostfolio/client/components/account-detail-dialog/interfaces/interfaces';
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import {
CreateAccountDto,
TransferBalanceDto,
UpdateAccountDto
} from '@ghostfolio/common/dtos';
import { User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfAccountsTableComponent } from '@ghostfolio/ui/accounts-table';

4
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.component.ts

@ -1,8 +1,6 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
import { DataService } from '@ghostfolio/client/services/data.service';
import { validateObjectForForm } from '@ghostfolio/client/util/form.util';
import { CreateAccountDto, UpdateAccountDto } from '@ghostfolio/common/dtos';
import { GfCurrencySelectorComponent } from '@ghostfolio/ui/currency-selector';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';

3
apps/client/src/app/pages/accounts/transfer-balance/transfer-balance-dialog.component.ts

@ -1,5 +1,4 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
import { TransferBalanceDto } from '@ghostfolio/common/dtos';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import {

18
apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.component.ts

@ -0,0 +1,18 @@
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { RouterModule } from '@angular/router';
@Component({
host: { class: 'page' },
imports: [GfPremiumIndicatorComponent, MatButtonModule, RouterModule],
selector: 'gf-black-weeks-2025-page',
templateUrl: './black-weeks-2025-page.html'
})
export class BlackWeeks2025PageComponent {
public routerLinkBlog = publicRoutes.blog.routerLink;
public routerLinkFeatures = publicRoutes.features.routerLink;
public routerLinkPricing = publicRoutes.pricing.routerLink;
}

159
apps/client/src/app/pages/blog/2025/11/black-weeks-2025/black-weeks-2025-page.html

@ -0,0 +1,159 @@
<div class="blog container">
<div class="row">
<div class="col-md-8 offset-md-2">
<article>
<div class="mb-4 text-center">
<h1 class="mb-1">Black Weeks 2025</h1>
<div class="mb-3 text-muted"><small>2025-11-16</small></div>
<img
alt="Black Week 2025 Teaser"
class="rounded w-100"
src="../assets/images/blog/black-weeks-2025.jpg"
title="Black Weeks 2025"
/>
</div>
<section class="mb-4">
<p>
Save <strong>25%</strong> on the
<span class="align-items-center d-inline-flex"
>Ghostfolio Premium
<gf-premium-indicator
class="d-inline-block ml-1"
[enableLink]="false"
/>
</span>
annual plan and get <strong>3 extra months</strong> on top with our
exclusive <strong>Black Weeks</strong> offer.
</p>
</section>
<section class="mb-4">
<p>
<a
href="https://ghostfol.io"
title="Open Source Wealth Management Software"
>Ghostfolio</a
>
unifies your finances in one place and gives you a clear overview of
your portfolio across stocks, ETFs, cryptocurrencies or other
assets. Real time analytics and smart evaluations help you
understand your financial situation quickly and make confident
decisions.
</p>
</section>
<section class="mb-4">
<p>
Grab this limited Black Weeks deal to optimize your financial
future.
</p>
<p class="text-center">
<a color="primary" mat-flat-button [routerLink]="routerLinkPricing"
>Get the offer</a
>
</p>
<p class="mt-5">
More details are available on the
<a [routerLink]="routerLinkPricing">pricing page</a>.
</p>
</section>
<section class="mb-4">
<ul class="list-inline">
<li class="list-inline-item">
<span class="badge badge-light">2025</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Black Friday</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Black Weeks</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Cryptocurrency</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Deal</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Discount</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">ETF</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Finance</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Fintech</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Ghostfolio</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Ghostfolio Premium</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Investment</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Open Source</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">OSS</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Personal Finance</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Portfolio</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Portfolio Tracker</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Pricing</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Promotion</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">SaaS</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Sale</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Savings</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Software</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Stock</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Subscription</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Wealth</span>
</li>
<li class="list-inline-item">
<span class="badge badge-light">Wealth Management</span>
</li>
</ul>
</section>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item">
<a i18n [routerLink]="routerLinkBlog">Blog</a>
</li>
<li
aria-current="page"
class="active breadcrumb-item text-truncate"
>
Black Weeks 2025
</li>
</ol>
</nav>
</article>
</div>
</div>
</div>

26
apps/client/src/app/pages/blog/blog-page.html

@ -8,6 +8,32 @@
finance</small
>
</h1>
@if (hasPermissionForSubscription) {
<mat-card appearance="outlined" class="mb-3">
<mat-card-content class="p-0">
<div class="container p-0">
<div class="flex-nowrap no-gutters row">
<a
class="d-flex overflow-hidden p-3 w-100"
href="../en/blog/2025/11/black-weeks-2025"
>
<div class="flex-grow-1 overflow-hidden">
<div class="h6 m-0 text-truncate">Black Weeks 2025</div>
<div class="d-flex text-muted">2025-11-15</div>
</div>
<div class="align-items-center d-flex">
<ion-icon
class="chevron text-muted"
name="chevron-forward-outline"
size="small"
/>
</div>
</a>
</div>
</div>
</mat-card-content>
</mat-card>
}
<mat-card appearance="outlined" class="mb-3">
<mat-card-content class="p-0">
<div class="container p-0">

9
apps/client/src/app/pages/blog/blog-page.routes.ts

@ -209,5 +209,14 @@ export const routes: Routes = [
'./2025/09/hacktoberfest-2025/hacktoberfest-2025-page.component'
).then((c) => c.Hacktoberfest2025PageComponent),
title: 'Hacktoberfest 2025'
},
{
canActivate: [AuthGuard],
path: '2025/11/black-weeks-2025',
loadComponent: () =>
import('./2025/11/black-weeks-2025/black-weeks-2025-page.component').then(
(c) => c.BlackWeeks2025PageComponent
),
title: 'Black Weeks 2025'
}
];

4
apps/client/src/app/pages/portfolio/activities/activities-page.component.ts

@ -1,11 +1,9 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { DataService } from '@ghostfolio/client/services/data.service';
import { IcsService } from '@ghostfolio/client/services/ics/ics.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos';
import { downloadAsFile } from '@ghostfolio/common/helper';
import {
Activity,

4
apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts

@ -1,8 +1,6 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { ASSET_CLASS_MAPPING } from '@ghostfolio/common/config';
import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos';
import { getDateFormatString } from '@ghostfolio/common/helper';
import {
AssetClassSelectorOption,

11
apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts

@ -1,14 +1,15 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto';
import { GfDialogFooterComponent } from '@ghostfolio/client/components/dialog-footer/dialog-footer.component';
import { GfDialogHeaderComponent } from '@ghostfolio/client/components/dialog-header/dialog-header.component';
import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive';
import { GfSymbolPipe } from '@ghostfolio/client/pipes/symbol/symbol.pipe';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service';
import {
CreateAccountWithBalancesDto,
CreateAssetProfileWithMarketDataDto,
CreateTagDto
} from '@ghostfolio/common/dtos';
import { Activity, PortfolioPosition } from '@ghostfolio/common/interfaces';
import { GfSymbolPipe } from '@ghostfolio/common/pipes';
import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table';
import {

3
apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.ts

@ -1,9 +1,8 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { GfRulesComponent } from '@ghostfolio/client/components/rules/rules.component';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { UpdateUserSettingDto } from '@ghostfolio/common/dtos';
import {
PortfolioReportResponse,
PortfolioReportRule

11
apps/client/src/app/services/admin.service.ts

@ -1,12 +1,13 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
import { CreatePlatformDto } from '@ghostfolio/api/app/platform/create-platform.dto';
import { UpdatePlatformDto } from '@ghostfolio/api/app/platform/update-platform.dto';
import {
DEFAULT_PAGE_SIZE,
HEADER_KEY_SKIP_INTERCEPTOR,
HEADER_KEY_TOKEN
} from '@ghostfolio/common/config';
import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import {
CreatePlatformDto,
UpdateAssetProfileDto,
UpdatePlatformDto
} from '@ghostfolio/common/dtos';
import {
AdminData,
AdminJobs,

39
apps/client/src/app/services/data.service.ts

@ -1,21 +1,21 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateAccessDto } from '@ghostfolio/api/app/access/create-access.dto';
import { UpdateAccessDto } from '@ghostfolio/api/app/access/update-access.dto';
import { CreateAccountBalanceDto } from '@ghostfolio/api/app/account-balance/create-account-balance.dto';
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { TransferBalanceDto } from '@ghostfolio/api/app/account/transfer-balance.dto';
import { UpdateAccountDto } from '@ghostfolio/api/app/account/update-account.dto';
import { UpdateBulkMarketDataDto } from '@ghostfolio/api/app/admin/update-bulk-market-data.dto';
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { UpdateTagDto } from '@ghostfolio/api/app/endpoints/tags/update-tag.dto';
import { CreateWatchlistItemDto } from '@ghostfolio/api/app/endpoints/watchlist/create-watchlist-item.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { DeleteOwnUserDto } from '@ghostfolio/api/app/user/delete-own-user.dto';
import { UserItem } from '@ghostfolio/api/app/user/interfaces/user-item.interface';
import { UpdateOwnAccessTokenDto } from '@ghostfolio/api/app/user/update-own-access-token.dto';
import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto';
import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import {
CreateAccessDto,
CreateAccountBalanceDto,
CreateAccountDto,
CreateOrderDto,
CreateTagDto,
CreateWatchlistItemDto,
DeleteOwnUserDto,
TransferBalanceDto,
UpdateAccessDto,
UpdateAccountDto,
UpdateBulkMarketDataDto,
UpdateOrderDto,
UpdateOwnAccessTokenDto,
UpdatePropertyDto,
UpdateTagDto,
UpdateUserSettingDto
} from '@ghostfolio/common/dtos';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import {
Access,
@ -52,6 +52,7 @@ import {
PublicPortfolioResponse,
SymbolItem,
User,
UserItem,
WatchlistResponse
} from '@ghostfolio/common/interfaces';
import { filterGlobalPermissions } from '@ghostfolio/common/permissions';
@ -808,7 +809,7 @@ export class DataService {
return this.http.put<UserItem>(`/api/v1/account/${aAccount.id}`, aAccount);
}
public putAdminSetting(key: string, aData: PropertyDto) {
public putAdminSetting(key: string, aData: UpdatePropertyDto) {
return this.http.put<void>(`/api/v1/admin/settings/${key}`, aData);
}

11
apps/client/src/app/services/import-activities.service.ts

@ -1,8 +1,9 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { CreateTagDto } from '@ghostfolio/api/app/endpoints/tags/create-tag.dto';
import { CreateAccountWithBalancesDto } from '@ghostfolio/api/app/import/create-account-with-balances.dto';
import { CreateAssetProfileWithMarketDataDto } from '@ghostfolio/api/app/import/create-asset-profile-with-market-data.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import {
CreateAccountWithBalancesDto,
CreateAssetProfileWithMarketDataDto,
CreateOrderDto,
CreateTagDto
} from '@ghostfolio/common/dtos';
import { parseDate as parseDateHelper } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';

7
apps/client/src/app/services/web-authn.service.ts

@ -1,10 +1,9 @@
/* eslint-disable @nx/enforce-module-boundaries */
import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto';
import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service';
import { AuthDeviceDto } from '@ghostfolio/common/dtos';
import {
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON
} from '@ghostfolio/api/app/auth/interfaces/simplewebauthn';
import { SettingsStorageService } from '@ghostfolio/client/services/settings-storage.service';
} from '@ghostfolio/common/interfaces';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

BIN
apps/client/src/assets/images/blog/black-weeks-2025.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

1
apps/client/src/polyfills.ts

@ -52,3 +52,4 @@ import 'zone.js'; // Included with Angular CLI.
*/
import '@angular/localize/init';
import 'reflect-metadata';

0
apps/api/src/app/auth-device/auth-device.dto.ts → libs/common/src/lib/dtos/auth-device.dto.ts

0
apps/api/src/app/access/create-access.dto.ts → libs/common/src/lib/dtos/create-access.dto.ts

0
apps/api/src/app/account-balance/create-account-balance.dto.ts → libs/common/src/lib/dtos/create-account-balance.dto.ts

3
apps/api/src/app/import/create-account-with-balances.dto.ts → libs/common/src/lib/dtos/create-account-with-balances.dto.ts

@ -1,8 +1,9 @@
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { AccountBalance } from '@ghostfolio/common/interfaces';
import { IsArray, IsOptional } from 'class-validator';
import { CreateAccountDto } from './create-account.dto';
export class CreateAccountWithBalancesDto extends CreateAccountDto {
@IsArray()
@IsOptional()

2
apps/api/src/app/account/create-account.dto.ts → libs/common/src/lib/dtos/create-account.dto.ts

@ -1,4 +1,4 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import { Transform, TransformFnParams } from 'class-transformer';
import {

2
apps/api/src/app/import/create-asset-profile-with-market-data.dto.ts → libs/common/src/lib/dtos/create-asset-profile-with-market-data.dto.ts

@ -3,7 +3,7 @@ import { MarketData } from '@ghostfolio/common/interfaces';
import { DataSource } from '@prisma/client';
import { IsArray, IsEnum, IsOptional } from 'class-validator';
import { CreateAssetProfileDto } from '../admin/create-asset-profile.dto';
import { CreateAssetProfileDto } from './create-asset-profile.dto';
export class CreateAssetProfileWithMarketDataDto extends CreateAssetProfileDto {
@IsEnum([DataSource.MANUAL], {

2
apps/api/src/app/admin/create-asset-profile.dto.ts → libs/common/src/lib/dtos/create-asset-profile.dto.ts

@ -1,4 +1,4 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client';
import {

2
apps/api/src/app/order/create-order.dto.ts → libs/common/src/lib/dtos/create-order.dto.ts

@ -1,5 +1,5 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';

0
apps/api/src/app/platform/create-platform.dto.ts → libs/common/src/lib/dtos/create-platform.dto.ts

0
apps/api/src/app/endpoints/tags/create-tag.dto.ts → libs/common/src/lib/dtos/create-tag.dto.ts

0
apps/api/src/app/endpoints/watchlist/create-watchlist-item.dto.ts → libs/common/src/lib/dtos/create-watchlist-item.dto.ts

0
apps/api/src/app/user/delete-own-user.dto.ts → libs/common/src/lib/dtos/delete-own-user.dto.ts

51
libs/common/src/lib/dtos/index.ts

@ -0,0 +1,51 @@
import { AuthDeviceDto } from './auth-device.dto';
import { CreateAccessDto } from './create-access.dto';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto';
import { CreateAccountDto } from './create-account.dto';
import { CreateAssetProfileWithMarketDataDto } from './create-asset-profile-with-market-data.dto';
import { CreateAssetProfileDto } from './create-asset-profile.dto';
import { CreateOrderDto } from './create-order.dto';
import { CreatePlatformDto } from './create-platform.dto';
import { CreateTagDto } from './create-tag.dto';
import { CreateWatchlistItemDto } from './create-watchlist-item.dto';
import { DeleteOwnUserDto } from './delete-own-user.dto';
import { TransferBalanceDto } from './transfer-balance.dto';
import { UpdateAccessDto } from './update-access.dto';
import { UpdateAccountDto } from './update-account.dto';
import { UpdateAssetProfileDto } from './update-asset-profile.dto';
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto';
import { UpdateMarketDataDto } from './update-market-data.dto';
import { UpdateOrderDto } from './update-order.dto';
import { UpdateOwnAccessTokenDto } from './update-own-access-token.dto';
import { UpdatePlatformDto } from './update-platform.dto';
import { UpdatePropertyDto } from './update-property.dto';
import { UpdateTagDto } from './update-tag.dto';
import { UpdateUserSettingDto } from './update-user-setting.dto';
export {
AuthDeviceDto,
CreateAccessDto,
CreateAccountBalanceDto,
CreateAccountDto,
CreateAccountWithBalancesDto,
CreateAssetProfileDto,
CreateAssetProfileWithMarketDataDto,
CreateOrderDto,
CreatePlatformDto,
CreateTagDto,
CreateWatchlistItemDto,
DeleteOwnUserDto,
TransferBalanceDto,
UpdateAccessDto,
UpdateAccountDto,
UpdateAssetProfileDto,
UpdateBulkMarketDataDto,
UpdateMarketDataDto,
UpdateOrderDto,
UpdateOwnAccessTokenDto,
UpdatePlatformDto,
UpdatePropertyDto,
UpdateTagDto,
UpdateUserSettingDto
};

0
apps/api/src/app/account/transfer-balance.dto.ts → libs/common/src/lib/dtos/transfer-balance.dto.ts

0
apps/api/src/app/access/update-access.dto.ts → libs/common/src/lib/dtos/update-access.dto.ts

2
apps/api/src/app/account/update-account.dto.ts → libs/common/src/lib/dtos/update-account.dto.ts

@ -1,4 +1,4 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import { Transform, TransformFnParams } from 'class-transformer';
import {

2
apps/api/src/app/admin/update-asset-profile.dto.ts → libs/common/src/lib/dtos/update-asset-profile.dto.ts

@ -1,4 +1,4 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client';
import {

4
apps/api/src/app/admin/update-bulk-market-data.dto.ts → libs/common/src/lib/dtos/update-bulk-market-data.dto.ts

@ -1,8 +1,8 @@
import { UpdateMarketDataDto } from '@ghostfolio/common/dtos';
import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray } from 'class-validator';
import { UpdateMarketDataDto } from './update-market-data.dto';
export class UpdateBulkMarketDataDto {
@ArrayNotEmpty()
@IsArray()

0
apps/api/src/app/admin/update-market-data.dto.ts → libs/common/src/lib/dtos/update-market-data.dto.ts

2
apps/api/src/app/order/update-order.dto.ts → libs/common/src/lib/dtos/update-order.dto.ts

@ -1,5 +1,5 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import { AssetClass, AssetSubClass, DataSource, Type } from '@prisma/client';
import { Transform, TransformFnParams } from 'class-transformer';

0
apps/api/src/app/user/update-own-access-token.dto.ts → libs/common/src/lib/dtos/update-own-access-token.dto.ts

0
apps/api/src/app/platform/update-platform.dto.ts → libs/common/src/lib/dtos/update-platform.dto.ts

2
apps/api/src/services/property/property.dto.ts → libs/common/src/lib/dtos/update-property.dto.ts

@ -1,6 +1,6 @@
import { IsOptional, IsString } from 'class-validator';
export class PropertyDto {
export class UpdatePropertyDto {
@IsOptional()
@IsString()
value: string;

0
apps/api/src/app/endpoints/tags/update-tag.dto.ts → libs/common/src/lib/dtos/update-tag.dto.ts

2
apps/api/src/app/user/update-user-setting.dto.ts → libs/common/src/lib/dtos/update-user-setting.dto.ts

@ -1,4 +1,3 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { XRayRulesSettings } from '@ghostfolio/common/interfaces';
import type {
ColorScheme,
@ -6,6 +5,7 @@ import type {
HoldingsViewMode,
ViewMode
} from '@ghostfolio/common/types';
import { IsCurrencyCode } from '@ghostfolio/common/validators/is-currency-code';
import {
IsArray,

16
libs/common/src/lib/interfaces/index.ts

@ -68,7 +68,7 @@ import type { MarketDataDetailsResponse } from './responses/market-data-details-
import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface';
import type { OAuthResponse } from './responses/oauth-response.interface';
import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface';
import { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface';
import type { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface';
import type { PortfolioHoldingsResponse } from './responses/portfolio-holdings-response.interface';
import type { PortfolioInvestmentsResponse } from './responses/portfolio-investments.interface';
import type { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
@ -76,7 +76,14 @@ import type { PortfolioReportResponse } from './responses/portfolio-report.inter
import type { PublicPortfolioResponse } from './responses/public-portfolio-response.interface';
import type { QuotesResponse } from './responses/quotes-response.interface';
import type { WatchlistResponse } from './responses/watchlist-response.interface';
import type { RuleSettings } from './rule-settings.interface';
import type { ScraperConfiguration } from './scraper-configuration.interface';
import type {
AssertionCredentialJSON,
AttestationCredentialJSON,
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON
} from './simplewebauthn.interface';
import type { Statistics } from './statistics.interface';
import type { SubscriptionOffer } from './subscription-offer.interface';
import type { SymbolItem } from './symbol-item.interface';
@ -84,6 +91,7 @@ import type { SymbolMetrics } from './symbol-metrics.interface';
import type { SystemMessage } from './system-message.interface';
import type { TabConfiguration } from './tab-configuration.interface';
import type { ToggleOption } from './toggle-option.interface';
import type { UserItem } from './user-item.interface';
import type { UserSettings } from './user-settings.interface';
import type { User } from './user.interface';
import type { XRayRulesSettings } from './x-ray-rules-settings.interface';
@ -109,9 +117,11 @@ export {
AdminUsersResponse,
AiPromptResponse,
ApiKeyResponse,
AssertionCredentialJSON,
AssetClassSelectorOption,
AssetProfileIdentifier,
AssetResponse,
AttestationCredentialJSON,
Benchmark,
BenchmarkMarketDataDetailsResponse,
BenchmarkProperty,
@ -160,9 +170,12 @@ export {
PortfolioSummary,
Position,
Product,
PublicKeyCredentialCreationOptionsJSON,
PublicKeyCredentialRequestOptionsJSON,
PublicPortfolioResponse,
QuotesResponse,
ResponseError,
RuleSettings,
ScraperConfiguration,
Statistics,
SubscriptionOffer,
@ -172,6 +185,7 @@ export {
TabConfiguration,
ToggleOption,
User,
UserItem,
UserSettings,
WatchlistResponse,
XRayRulesSettings

0
apps/api/src/models/interfaces/rule-settings.interface.ts → libs/common/src/lib/interfaces/rule-settings.interface.ts

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save