Browse Source

Merge remote-tracking branch 'upstream/main' into feature/compare-with-multiple-benchmarks

pull/8238/head
mister-ideas 3 days ago
parent
commit
b525fd7a99
  1. 16
      CHANGELOG.md
  2. 1211
      apps/api/src/app/__snapshots__/app.module.spec.ts.snap
  3. 2
      apps/api/src/app/activities/activities.module.ts
  4. 212
      apps/api/src/app/app.module.spec.ts
  5. 4
      apps/api/src/app/app.module.ts
  6. 2
      apps/api/src/app/auth/auth.module.ts
  7. 2
      apps/api/src/app/auth/web-auth.service.ts
  8. 2
      apps/api/src/app/cache/cache.controller.ts
  9. 2
      apps/api/src/app/cache/cache.module.ts
  10. 2
      apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts
  11. 2
      apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts
  12. 4
      apps/api/src/app/endpoints/platforms/platform.controller.ts
  13. 113
      apps/api/src/app/endpoints/platforms/platforms.controller.ts
  14. 5
      apps/api/src/app/endpoints/platforms/platforms.module.ts
  15. 2
      apps/api/src/app/endpoints/public/public.module.ts
  16. 2
      apps/api/src/app/health/health.module.ts
  17. 2
      apps/api/src/app/health/health.service.ts
  18. 4
      apps/api/src/app/import/import.module.ts
  19. 2
      apps/api/src/app/import/import.service.ts
  20. 4
      apps/api/src/app/info/info.module.ts
  21. 2
      apps/api/src/app/info/info.service.ts
  22. 2
      apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
  23. 2
      apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
  24. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts
  25. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
  26. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
  27. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
  28. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts
  29. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
  30. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  31. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-before-first-market-price.spec.ts
  32. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts
  33. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
  34. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
  35. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts
  36. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts
  37. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
  38. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts
  39. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts
  40. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts
  41. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-before-first-market-price.spec.ts
  42. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-with-market-price-between-chart-dates.spec.ts
  43. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts
  44. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts
  45. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
  46. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-activities.spec.ts
  47. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
  48. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
  49. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-stock-split.spec.ts
  50. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts
  51. 6
      apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts
  52. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-buy.spec.ts
  53. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-sell.spec.ts
  54. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy.spec.ts
  55. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-sell-and-buy-on-same-day.spec.ts
  56. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
  57. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-cash.spec.ts
  58. 6
      apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-msft-buy-with-dividend.spec.ts
  59. 2
      apps/api/src/app/portfolio/portfolio.module.ts
  60. 2
      apps/api/src/events/events.module.ts
  61. 2
      apps/api/src/events/portfolio-changed.listener.ts
  62. 2
      apps/api/src/services/benchmark/benchmark.module.ts
  63. 2
      apps/api/src/services/benchmark/benchmark.service.ts
  64. 2
      apps/api/src/services/data-provider/data-provider.module.ts
  65. 2
      apps/api/src/services/data-provider/data-provider.service.spec.ts
  66. 2
      apps/api/src/services/data-provider/data-provider.service.ts
  67. 2
      apps/api/src/services/platform/platform.module.ts
  68. 7
      apps/api/src/services/platform/platform.service.ts
  69. 2
      apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts
  70. 2
      apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts
  71. 2
      apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts
  72. 0
      apps/api/src/services/redis-cache/redis-cache.module.ts
  73. 0
      apps/api/src/services/redis-cache/redis-cache.service.mock.ts
  74. 0
      apps/api/src/services/redis-cache/redis-cache.service.ts
  75. 18
      apps/client/src/app/app.component.ts
  76. 1
      apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
  77. 20
      apps/client/src/app/components/admin-platform/admin-platform.component.ts
  78. 2
      apps/client/src/app/components/admin-users/admin-users.html
  79. 1
      apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
  80. 1
      apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html
  81. 4
      apps/client/src/app/components/portfolio-performance/portfolio-performance.component.scss
  82. 6
      apps/client/src/app/components/user-account-access/create-or-update-access-dialog/create-or-update-access-dialog.component.ts
  83. 67
      apps/client/src/app/components/user-account-settings/user-account-settings.html
  84. 13
      apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts
  85. 2
      libs/common/src/lib/interfaces/index.ts
  86. 6
      libs/common/src/lib/interfaces/responses/platform-response.interface.ts
  87. 4
      libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
  88. 9
      libs/ui/src/lib/account-balances/account-balances.component.ts
  89. 13
      libs/ui/src/lib/file-drop-area/file-drop-area.component.html
  90. 94
      libs/ui/src/lib/file-drop-area/file-drop-area.component.scss
  91. 6
      libs/ui/src/lib/file-drop-area/file-drop-area.component.stories.ts
  92. 3
      libs/ui/src/lib/file-drop-area/file-drop-area.component.ts
  93. 11
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts
  94. 2
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
  95. 6
      libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
  96. 29
      libs/ui/src/lib/services/admin.service.ts
  97. 18
      libs/ui/src/lib/services/data.service.ts
  98. 47
      package-lock.json
  99. 8
      package.json

16
CHANGELOG.md

@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Changed
- Upgraded `@modelcontextprotocol/core` and `@modelcontextprotocol/server` from version `2.0.0` to `2.3.1`
- Upgraded `@modelcontextprotocol/node` from version `2.0.0` to `2.1.1`
## 3.89.0 - 2026-10-20
### Added
- Added support for comparing the performance with multiple benchmarks in the benchmark comparator of the analysis page
@ -14,12 +21,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the layout of the user account settings on mobile
- Improved the usability of the activities page by eliminating the reload on canceling the import activities dialog
- Improved the loading state of the file drop area component
- Improved the accessibility of the _Buy me a coffee_ button on the about page
- Improved the accessibility of the entity logo component
- Extracted the file drop area of the import activities dialog to a reusable component
- Improved the performance of the historical market data table of the admin control panel by loading only the latest market price per asset profile
- Introduced a cooldown of 12 hours for the asset profile and exchange rate gathering on activity creation by retaining the completed jobs
- Deprecated the `api/v1/platform` endpoints in favor of the `api/v1/platforms` endpoints
### Fixed
- Fixed the missing localization of the date picker in the _FIRE_ calculator
- Fixed the missing localization of the date picker in the asset profile details dialog of the admin control panel
- Fixed the missing tooltip of the client of the last request in the users table of the admin control panel
## 3.88.0 - 2026-10-17

1211
apps/api/src/app/__snapshots__/app.module.spec.ts.snap

File diff suppressed because it is too large

2
apps/api/src/app/activities/activities.module.ts

@ -1,7 +1,6 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CacheModule } from '@ghostfolio/api/app/cache/cache.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
@ -14,6 +13,7 @@ import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impe
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';

212
apps/api/src/app/app.module.spec.ts

@ -1,17 +1,26 @@
import { ALLOW_DURING_IMPERSONATION_KEY } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator';
import { HAS_PERMISSION_KEY } from '@ghostfolio/api/decorators/has-permission.decorator';
import { REQUIRES_SCOPE_KEY } from '@ghostfolio/api/decorators/requires-scope.decorator';
import {
DynamicModule,
ForwardReference,
RequestMethod,
Type
Type,
VERSION_NEUTRAL
} from '@nestjs/common';
import {
GUARDS_METADATA,
INTERCEPTORS_METADATA,
METHOD_METADATA,
MODULE_METADATA,
PATH_METADATA
PATH_METADATA,
VERSION_METADATA
} from '@nestjs/common/constants';
import { VersionValue } from '@nestjs/common/interfaces';
import { MetadataScanner } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { isUndefined, omitBy } from 'lodash-es';
import { AppModule } from './app.module';
@ -26,6 +35,13 @@ jest.mock('ai', () => {
type ModuleDefinition =
DynamicModule | ForwardReference<() => Type> | Promise<DynamicModule> | Type;
/**
* The strategies of the authentication which the routes apply via
* AuthGuard(). The class of such a guard has a random name, hence the
* snapshot of the routes names it by its strategy
*/
const AUTHENTICATION_STRATEGIES = ['api-key', 'google', 'jwt', 'oidc'];
/**
* The routes of the controllers which answer a request without the
* authentication of a user or of an API key, sorted by the request method and
@ -98,6 +114,57 @@ async function getControllers(
return [...new Set(controllers)];
}
/**
* Gives the guards or the interceptors which the decorators of the controller
* and of the route handler set, in the order in which they run
*/
function getEnhancers(key: string, controller: Type, routeHandler: object) {
return [controller, routeHandler].flatMap((target) => {
return (Reflect.getMetadata(key, target) ?? []) as Type[];
});
}
/**
* Gives the metadata of the route handler or, if it has none, of the
* controller, as Reflector.getAllAndOverride() does
*/
function getMetadataOfRoute<T>(
key: string,
controller: Type,
routeHandler: object
) {
return (Reflect.getMetadata(key, routeHandler) ??
Reflect.getMetadata(key, controller)) as T | undefined;
}
/**
* Gives the name of a guard or of an interceptor. The function mixin()
* replaces the name of a class by a random one, hence the name is read from
* the declaration of the class. A guard which extends AuthGuard() is named
* with its strategy
*/
function getName(enhancer: Type) {
const authenticationStrategy = AUTHENTICATION_STRATEGIES.find((strategy) => {
return AuthGuard(strategy) === enhancer;
});
if (authenticationStrategy) {
return `AuthGuard('${authenticationStrategy}')`;
}
const name = /^class\s+([\w$]+)/.exec(enhancer.toString())?.[1];
if (!name || name === 'MixinAuthGuard') {
throw new Error('The name of a guard or of an interceptor is unknown');
}
const parentStrategy = AUTHENTICATION_STRATEGIES.find((strategy) => {
return Object.getPrototypeOf(enhancer) === AuthGuard(strategy);
});
return parentStrategy ? `${name}('${parentStrategy}')` : name;
}
/**
* Gives the paths which the decorator of a controller or of a route sets
*/
@ -108,14 +175,10 @@ function getPaths(target: object) {
}
/**
* Gives the routes of the controller which apply neither AuthGuard('api-key')
* nor AuthGuard('jwt'), each as the request method and the path
* Gives the routes of the controller, each as the request method and the path
* with its route handler
*/
function getRoutesWithoutAuthentication(controller: Type) {
const authenticationGuards: unknown[] = [
AuthGuard('api-key'),
AuthGuard('jwt')
];
function getRoutes(controller: Type) {
const routeHandlersByName = controller.prototype as Record<string, object>;
return new MetadataScanner()
@ -131,19 +194,6 @@ function getRoutesWithoutAuthentication(controller: Type) {
return [];
}
const guards = [controller, routeHandler].flatMap((target) => {
return (Reflect.getMetadata(GUARDS_METADATA, target) ??
[]) as unknown[];
});
const hasAuthentication = guards.some((guard) => {
return authenticationGuards.includes(guard);
});
if (hasAuthentication) {
return [];
}
return getPaths(controller).flatMap((controllerPath) => {
return getPaths(routeHandler).map((routePath) => {
const path = `${controllerPath}/${routePath}`
@ -151,12 +201,110 @@ function getRoutesWithoutAuthentication(controller: Type) {
.filter(Boolean)
.join('/');
return `${RequestMethod[requestMethod]} /${path}`;
return {
routeHandler,
route: `${RequestMethod[requestMethod]} /${path}`
};
});
});
});
}
/**
* Gives the routes of the controller which apply neither AuthGuard('api-key')
* nor AuthGuard('jwt'), each as the request method and the path
*/
function getRoutesWithoutAuthentication(controller: Type) {
const authenticationGuards: unknown[] = [
AuthGuard('api-key'),
AuthGuard('jwt')
];
return getRoutes(controller)
.filter(({ routeHandler }) => {
const hasAuthentication = getEnhancers(
GUARDS_METADATA,
controller,
routeHandler
).some((guard) => {
return authenticationGuards.includes(guard);
});
return !hasAuthentication;
})
.map(({ route }) => {
return route;
});
}
/**
* Gives the routes of the controller, each as the request method, the path
* and the version with the guards and the interceptors in the order in which
* they run, the permission, the scopes and whether the route stays available
* while an impersonation is active
*/
function getRoutesWithSecurity(controller: Type) {
return getRoutes(controller).map(({ route, routeHandler }) => {
const security = omitBy(
{
allowDuringImpersonation: getMetadataOfRoute<boolean>(
ALLOW_DURING_IMPERSONATION_KEY,
controller,
routeHandler
),
guards: getEnhancers(GUARDS_METADATA, controller, routeHandler).map(
(guard) => {
return getName(guard);
}
),
interceptors: getEnhancers(
INTERCEPTORS_METADATA,
controller,
routeHandler
).map((interceptor) => {
return getName(interceptor);
}),
// HasPermissionGuard reads the permission of the route handler only
permission: Reflect.getMetadata(HAS_PERMISSION_KEY, routeHandler) as
string | undefined,
scopes: getMetadataOfRoute<string[]>(
REQUIRES_SCOPE_KEY,
controller,
routeHandler
)
},
isUndefined
);
return [
`${route}${getVersionSuffix(controller, routeHandler)}`,
security
] as const;
});
}
/**
* Gives the version of the route as a suffix of its key, which is empty for
* the default version
*/
function getVersionSuffix(controller: Type, routeHandler: object) {
const version = getMetadataOfRoute<VersionValue>(
VERSION_METADATA,
controller,
routeHandler
);
if (version === undefined) {
return '';
}
const versions = [version].flat().map((value) => {
return value === VERSION_NEUTRAL ? 'neutral' : value;
});
return ` (version ${versions.join(', ')})`;
}
describe('AppModule', () => {
it('should require the authentication for each route which is not public', async () => {
const controllers = await getControllers(AppModule);
@ -169,4 +317,22 @@ describe('AppModule', () => {
expect(routesWithoutAuthentication).toEqual(PUBLIC_ROUTES);
});
// An intended change of a route is confirmed by an update of the snapshot
// (--updateSnapshot), hence each change shows in the review
it('should keep the security of each route', async () => {
const controllers = await getControllers(AppModule);
const routesWithSecurity = controllers.flatMap((controller) => {
return getRoutesWithSecurity(controller);
});
const routes = routesWithSecurity.map(([route]) => {
return route;
});
// The snapshot is an object, which would hide a route declared twice
expect(new Set(routes).size).toBe(routes.length);
expect(Object.fromEntries(routesWithSecurity)).toMatchSnapshot();
});
});

4
apps/api/src/app/app.module.ts

@ -14,6 +14,7 @@ import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import {
BULL_BOARD_ROUTE,
THROTTLE_DEFAULT_LIMIT,
@ -62,9 +63,7 @@ import { HealthModule } from './health/health.module';
import { ImportModule } from './import/import.module';
import { InfoModule } from './info/info.module';
import { LogoModule } from './logo/logo.module';
import { PlatformModule } from './platform/platform.module';
import { PortfolioModule } from './portfolio/portfolio.module';
import { RedisCacheModule } from './redis-cache/redis-cache.module';
import { SubscriptionModule } from './subscription/subscription.module';
import { SymbolModule } from './symbol/symbol.module';
import { UserModule } from './user/user.module';
@ -130,7 +129,6 @@ import { UserModule } from './user/user.module';
LogoModule,
MarketDataModule,
McpModule,
PlatformModule,
PlatformsModule,
PortfolioModule,
PortfolioSnapshotQueueModule,

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

@ -1,6 +1,5 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service';
@ -12,6 +11,7 @@ import { FetchService } from '@ghostfolio/api/services/fetch/fetch.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { Logger, Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';

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

@ -1,10 +1,10 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import {
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,

2
apps/api/src/app/cache/cache.controller.ts

@ -1,7 +1,7 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { permissions } from '@ghostfolio/common/permissions';
import { Controller, Post, UseGuards } from '@nestjs/common';

2
apps/api/src/app/cache/cache.module.ts

@ -1,4 +1,4 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { Module } from '@nestjs/common';

2
apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts

@ -5,7 +5,6 @@ import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calcul
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
@ -22,6 +21,7 @@ import { MarketDataService } from '@ghostfolio/api/services/market-data/market-d
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';

2
apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts

@ -1,4 +1,3 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module';
@ -17,6 +16,7 @@ import { FetchModule } from '@ghostfolio/api/services/fetch/fetch.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';

4
apps/api/src/app/platform/platform.controller.ts → apps/api/src/app/endpoints/platforms/platform.controller.ts

@ -1,6 +1,7 @@
import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { PlatformService } from '@ghostfolio/api/services/platform/platform.service';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { permissions } from '@ghostfolio/common/permissions';
@ -19,8 +20,7 @@ import { AuthGuard } from '@nestjs/passport';
import { Platform } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { PlatformService } from './platform.service';
/** @deprecated */
@AllowDuringImpersonation()
@Controller('platform')
export class PlatformController {

113
apps/api/src/app/endpoints/platforms/platforms.controller.ts

@ -1,24 +1,125 @@
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { AllowDuringImpersonation } from '@ghostfolio/api/decorators/allow-during-impersonation.decorator';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { PlatformsResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { PlatformService } from '@ghostfolio/api/services/platform/platform.service';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import {
PlatformResponse,
PlatformsResponse
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Get, UseGuards } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
HttpException,
Inject,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { pick } from 'lodash-es';
@AllowDuringImpersonation()
@Controller('platforms')
export class PlatformsController {
public constructor(private readonly platformService: PlatformService) {}
public constructor(
private readonly platformService: PlatformService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@HasPermission(permissions.createPlatform)
@Post()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createPlatform(
@Body() data: CreatePlatformDto
): Promise<PlatformResponse> {
const platform = await this.platformService.createPlatform(data);
return pick(platform, ['id', 'name', 'url']);
}
@Delete(':id')
@HasPermission(permissions.deletePlatform)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deletePlatform(@Param('id') id: string): Promise<void> {
const originalPlatform = await this.platformService.getPlatform({
id
});
if (!originalPlatform) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
await this.platformService.deletePlatform({ id });
}
@Get()
@HasPermission(permissions.readPlatforms)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getPlatforms(): Promise<PlatformsResponse> {
if (
hasPermission(
this.request.user.permissions,
permissions.readPlatformsWithAccountCount
)
) {
const platformsWithAccountCount =
await this.platformService.getPlatformsWithAccountCount({
orderBy: { name: 'asc' }
});
return { platforms: platformsWithAccountCount };
}
const platforms = await this.platformService.getPlatforms({
orderBy: { name: 'asc' }
});
return { platforms };
return {
platforms: platforms.map((platform) => {
return pick(platform, ['id', 'name', 'url']);
})
};
}
@HasPermission(permissions.updatePlatform)
@Put(':id')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updatePlatform(
@Param('id') id: string,
@Body() data: UpdatePlatformDto
): Promise<PlatformResponse> {
const originalPlatform = await this.platformService.getPlatform({
id
});
if (!originalPlatform) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const platform = await this.platformService.updatePlatform({
data: {
...data
},
where: {
id
}
});
return pick(platform, ['id', 'name', 'url']);
}
}

5
apps/api/src/app/endpoints/platforms/platforms.module.ts

@ -1,11 +1,12 @@
import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module';
import { PlatformModule } from '@ghostfolio/api/services/platform/platform.module';
import { Module } from '@nestjs/common';
import { PlatformController } from './platform.controller';
import { PlatformsController } from './platforms.controller';
@Module({
controllers: [PlatformsController],
controllers: [PlatformController, PlatformsController],
imports: [PlatformModule]
})
export class PlatformsModule {}

2
apps/api/src/app/endpoints/public/public.module.ts

@ -6,7 +6,6 @@ import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calcul
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { RulesService } from '@ghostfolio/api/app/portfolio/rules.service';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
@ -17,6 +16,7 @@ import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impe
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';

2
apps/api/src/app/health/health.module.ts

@ -1,9 +1,9 @@
import { AiModule } from '@ghostfolio/api/app/endpoints/ai/ai.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { DataEnhancerModule } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { Module } from '@nestjs/common';

2
apps/api/src/app/health/health.service.ts

@ -1,7 +1,7 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { DataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/data-enhancer.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { PROPERTY_CURRENCIES } from '@ghostfolio/common/config';
import { Injectable } from '@nestjs/common';

4
apps/api/src/app/import/import.module.ts

@ -1,9 +1,7 @@
import { AccountModule } from '@ghostfolio/api/app/account/account.module';
import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module';
import { CacheModule } from '@ghostfolio/api/app/cache/cache.module';
import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module';
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
@ -11,8 +9,10 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration/conf
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 { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PlatformModule } from '@ghostfolio/api/services/platform/platform.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';

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

@ -1,6 +1,5 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { getTagsWithDraftTag } from '@ghostfolio/api/helper/activity.helper';
import { getMaskedGhostfolioDataSource } from '@ghostfolio/api/helper/data-source.helper';
@ -9,6 +8,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con
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 { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PlatformService } from '@ghostfolio/api/services/platform/platform.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';

4
apps/api/src/app/info/info.module.ts

@ -1,5 +1,3 @@
import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
@ -8,8 +6,10 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration/conf
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 { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PlatformModule } from '@ghostfolio/api/services/platform/platform.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';

2
apps/api/src/app/info/info.service.ts

@ -1,4 +1,3 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
@ -7,6 +6,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import {
DEFAULT_CURRENCY,
ghostfolioFearAndGreedIndexSymbolStocks,

2
apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts

@ -1,8 +1,8 @@
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { SubscriptionType } from '@ghostfolio/common/enums';
import {
Activity,

2
apps/api/src/app/portfolio/calculator/portfolio-calculator.ts

@ -14,13 +14,13 @@ import { PortfolioCalculatorActivity } from '@ghostfolio/api/app/portfolio/inter
import { PortfolioCalculatorHolding } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-calculator-holding.interface';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { PerformancePercentages } from '@ghostfolio/api/app/portfolio/types/performance-percentages.type';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { getFactor } from '@ghostfolio/api/helper/portfolio.helper';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import {
getAnnualizedPerformancePercent,
getIntervalFromDateRange

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -37,7 +37,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -36,7 +36,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts

@ -7,13 +7,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -51,7 +51,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts

@ -7,12 +7,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity, ExportResponse } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -39,7 +39,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -6,13 +6,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-before-first-market-price.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts

@ -9,8 +9,6 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { AssetProfileSplitService } from '@ghostfolio/api/services/asset-profile-split/asset-profile-split.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
@ -18,6 +16,8 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PortfolioSnapshotHolding } from '@ghostfolio/common/models';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -57,7 +57,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -37,7 +37,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts

@ -6,13 +6,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts

@ -6,13 +6,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts

@ -7,12 +7,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -37,7 +37,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-before-first-market-price.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-with-market-price-between-chart-dates.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -37,7 +37,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts

@ -6,13 +6,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -36,7 +36,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-activities.spec.ts

@ -2,12 +2,12 @@ import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfoli
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -32,7 +32,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-stock-split.spec.ts

@ -6,13 +6,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { adjustActivityBySplits } from '@ghostfolio/api/services/asset-profile-split/asset-profile-split.helper';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -39,7 +39,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -37,7 +37,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts

@ -7,12 +7,12 @@ import { userDummyData } from '@ghostfolio/api/app/portfolio/calculator/portfoli
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { HistoricalDataItem } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -36,7 +36,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-buy.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-sell.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -36,7 +36,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy.spec.ts

@ -5,12 +5,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -35,7 +35,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-sell-and-buy-on-same-day.spec.ts

@ -7,12 +7,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { Activity } from '@ghostfolio/common/interfaces';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts

@ -6,13 +6,13 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -38,7 +38,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-cash.spec.ts

@ -10,8 +10,6 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { AssetProfileSplitService } from '@ghostfolio/api/services/asset-profile-split/asset-profile-split.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
@ -19,6 +17,8 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { ExchangeRateDataServiceMock } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service.mock';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PortfolioSnapshotHolding } from '@ghostfolio/common/models';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -58,7 +58,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

6
apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-msft-buy-with-dividend.spec.ts

@ -6,12 +6,12 @@ import {
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
import { CurrentRateServiceMock } from '@ghostfolio/api/app/portfolio/current-rate.service.mock';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { PortfolioSnapshotServiceMock } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import { parseDate } from '@ghostfolio/common/helper';
import { PerformanceCalculationType } from '@ghostfolio/common/types/performance-calculation-type.type';
@ -36,7 +36,7 @@ jest.mock(
}
);
jest.mock('@ghostfolio/api/app/redis-cache/redis-cache.service', () => {
jest.mock('@ghostfolio/api/services/redis-cache/redis-cache.service', () => {
return {
RedisCacheService: jest.fn().mockImplementation(() => {
return RedisCacheServiceMock;

2
apps/api/src/app/portfolio/portfolio.module.ts

@ -2,7 +2,6 @@ import { AccessModule } from '@ghostfolio/api/app/access/access.module';
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { PerformanceLoggingModule } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
@ -19,6 +18,7 @@ import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-da
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';

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

@ -1,5 +1,4 @@
import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
@ -7,6 +6,7 @@ import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { Module } from '@nestjs/common';

2
apps/api/src/events/portfolio-changed.listener.ts

@ -1,7 +1,7 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import {
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW,
PORTFOLIO_SNAPSHOT_PROCESS_JOB_NAME,

2
apps/api/src/services/benchmark/benchmark.module.ts

@ -1,8 +1,8 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';

2
apps/api/src/services/benchmark/benchmark.service.ts

@ -1,8 +1,8 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import {
CACHE_TTL_INFINITE,

2
apps/api/src/services/data-provider/data-provider.module.ts

@ -1,4 +1,3 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module';
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
@ -14,6 +13,7 @@ import { FetchModule } from '@ghostfolio/api/services/fetch/fetch.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';

2
apps/api/src/services/data-provider/data-provider.service.spec.ts

@ -1,9 +1,9 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import { DataSource } from '@prisma/client';

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

@ -1,12 +1,12 @@
import { ImportValidationError } from '@ghostfolio/api/app/import/errors/import-validation.error';
import { ImportDataDto } from '@ghostfolio/api/app/import/import-data.dto';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { getMaskedGhostfolioDataSource } from '@ghostfolio/api/helper/data-source.helper';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import {
DEFAULT_CURRENCY,
DERIVED_CURRENCIES,

2
apps/api/src/app/platform/platform.module.ts → apps/api/src/services/platform/platform.module.ts

@ -2,11 +2,9 @@ import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { Module } from '@nestjs/common';
import { PlatformController } from './platform.controller';
import { PlatformService } from './platform.service';
@Module({
controllers: [PlatformController],
exports: [PlatformService],
imports: [PrismaModule],
providers: [PlatformService]

7
apps/api/src/app/platform/platform.service.ts → apps/api/src/services/platform/platform.service.ts

@ -49,9 +49,14 @@ export class PlatformService {
});
}
public async getPlatformsWithAccountCount() {
public async getPlatformsWithAccountCount({
orderBy
}: {
orderBy?: Prisma.PlatformOrderByWithRelationInput;
} = {}) {
const platformsWithAccountCount =
await this.prismaService.platform.findMany({
orderBy,
include: {
_count: {
select: { accounts: true }

2
apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts

@ -2,12 +2,12 @@ import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/accoun
import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { CurrentRateService } from '@ghostfolio/api/app/portfolio/current-rate.service';
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 { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PortfolioSnapshotService } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.service';
import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module';
import {
DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT,
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE

2
apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts

@ -2,8 +2,8 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/accou
import { ActivitiesService } from '@ghostfolio/api/app/activities/activities.service';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { RedisCacheService } from '@ghostfolio/api/services/redis-cache/redis-cache.service';
import {
CACHE_TTL_INFINITE,
DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY,

2
apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts

@ -1,5 +1,5 @@
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { RedisCacheServiceMock } from '@ghostfolio/api/app/redis-cache/redis-cache.service.mock';
import { RedisCacheServiceMock } from '@ghostfolio/api/services/redis-cache/redis-cache.service.mock';
import type { Job, JobId, JobOptions } from 'bull';
import ms from 'ms';

0
apps/api/src/app/redis-cache/redis-cache.module.ts → apps/api/src/services/redis-cache/redis-cache.module.ts

0
apps/api/src/app/redis-cache/redis-cache.service.mock.ts → apps/api/src/services/redis-cache/redis-cache.service.mock.ts

0
apps/api/src/app/redis-cache/redis-cache.service.ts → apps/api/src/services/redis-cache/redis-cache.service.ts

18
apps/client/src/app/app.component.ts

@ -25,6 +25,7 @@ import {
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DateAdapter } from '@angular/material/core';
import { MatDialog } from '@angular/material/dialog';
import { Title } from '@angular/platform-browser';
import {
@ -39,7 +40,7 @@ import { Chart } from 'chart.js';
import { addIcons } from 'ionicons';
import { openOutline } from 'ionicons/icons';
import { DeviceDetectorService } from 'ngx-device-detector';
import { filter } from 'rxjs/operators';
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
import { GfFooterComponent } from './components/footer/footer.component';
import { GfHeaderComponent } from './components/header/header.component';
@ -75,6 +76,7 @@ export class GfAppComponent implements OnInit {
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly dateAdapter = inject<DateAdapter<Date, string>>(DateAdapter);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog);
@ -212,6 +214,20 @@ export class GfAppComponent implements OnInit {
this.changeDetectorRef.markForCheck();
});
this.userService.stateChanged
.pipe(
map(({ user }) => {
return user?.settings.locale;
}),
distinctUntilChanged(),
takeUntilDestroyed(this.destroyRef)
)
.subscribe((locale) => {
if (locale) {
this.dateAdapter.setLocale(locale);
}
});
this.userService.stateChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {

1
apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html

@ -505,7 +505,6 @@
[locale]="data.locale"
[marketData]="marketDataItems"
[symbol]="data.symbol"
[user]="user"
(marketDataChanged)="onMarketDataChanged($event)"
/>
</div>

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

@ -3,10 +3,11 @@ import { DEFAULT_PAGE_SIZE } from '@ghostfolio/common/config';
import { CreatePlatformDto, UpdatePlatformDto } from '@ghostfolio/common/dtos';
import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { getLocale, getLowercase } from '@ghostfolio/common/helper';
import { PlatformsResponse } from '@ghostfolio/common/interfaces';
import type { Platform } from '@ghostfolio/prisma/browser';
import { GfEntityLogoComponent } from '@ghostfolio/ui/entity-logo';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { AdminService, DataService } from '@ghostfolio/ui/services';
import { DataService } from '@ghostfolio/ui/services';
import { GfValueComponent } from '@ghostfolio/ui/value';
import {
@ -62,7 +63,9 @@ import { CreateOrUpdatePlatformDialogParams } from './create-or-update-platform-
export class GfAdminPlatformComponent implements OnInit {
public readonly locale = input(getLocale());
protected dataSource = new MatTableDataSource<Platform>();
protected dataSource = new MatTableDataSource<
PlatformsResponse['platforms'][0]
>();
protected readonly displayedColumns = [
'icon',
@ -74,7 +77,7 @@ export class GfAdminPlatformComponent implements OnInit {
protected isLoading = false;
protected readonly pageSize = DEFAULT_PAGE_SIZE;
protected platforms: Platform[];
protected platforms: PlatformsResponse['platforms'];
private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
@ -83,7 +86,6 @@ export class GfAdminPlatformComponent implements OnInit {
private readonly paginator = viewChild.required(MatPaginator);
private readonly sort = viewChild.required(MatSort);
private readonly adminService = inject(AdminService);
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
@ -139,7 +141,7 @@ export class GfAdminPlatformComponent implements OnInit {
}
private deletePlatform(aId: string) {
this.adminService
this.dataService
.deletePlatform(aId)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
@ -157,10 +159,10 @@ export class GfAdminPlatformComponent implements OnInit {
private fetchPlatforms() {
this.isLoading = true;
this.adminService
this.dataService
.fetchPlatforms()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((platforms) => {
.subscribe(({ platforms }) => {
this.platforms = platforms;
this.dataSource = new MatTableDataSource(platforms);
@ -191,7 +193,7 @@ export class GfAdminPlatformComponent implements OnInit {
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((platform: CreatePlatformDto | null) => {
if (platform) {
this.adminService
this.dataService
.postPlatform(platform)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
@ -231,7 +233,7 @@ export class GfAdminPlatformComponent implements OnInit {
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((platform: UpdatePlatformDto | null) => {
if (platform) {
this.adminService
this.dataService
.putPlatform(platform)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({

2
apps/client/src/app/components/admin-users/admin-users.html

@ -199,7 +199,7 @@
<div class="align-items-center d-flex">
@if (element.lastRequestClient) {
<ion-icon
class="me-1"
class="me-1 pe-auto"
[name]="requestClientIcons[element.lastRequestClient]"
[title]="translate(element.lastRequestClient)"
/>

1
apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html

@ -437,7 +437,6 @@
[locale]="data.locale"
[marketData]="marketDataItems"
[symbol]="assetProfile?.symbol"
[user]="user"
(marketDataChanged)="onMarketDataChanged($event)"
/>
</mat-tab>

1
apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html

@ -3,6 +3,7 @@
<div class="px-1 status-container text-body-secondary text-end">
@if ((errors()?.length ?? 0) > 0 && !isLoading()) {
<ion-icon
class="pe-auto"
i18n-title
name="time-outline"
title="Oops! A data provider is experiencing the hiccups."

4
apps/client/src/app/components/portfolio-performance/portfolio-performance.component.scss

@ -8,10 +8,6 @@
.status-container {
font-size: 1.33rem;
ion-icon {
pointer-events: all;
}
}
.value-container {

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

@ -1,5 +1,5 @@
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_LOCALE, MCP_ENDPOINT } from '@ghostfolio/common/config';
import { MCP_ENDPOINT } from '@ghostfolio/common/config';
import { CreateAccessDto, UpdateAccessDto } from '@ghostfolio/common/dtos';
import { canApplyFiltersToAccess } from '@ghostfolio/common/helper';
import { Filter, PortfolioPosition } from '@ghostfolio/common/interfaces';
@ -48,7 +48,6 @@ import {
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { DateAdapter } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import {
MAT_DIALOG_DATA,
@ -108,7 +107,6 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
private readonly data =
inject<CreateOrUpdateAccessDialogParams>(MAT_DIALOG_DATA);
private readonly dateAdapter = inject<DateAdapter<Date, string>>(DateAdapter);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
@ -189,8 +187,6 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit {
this.hasExperimentalFeatures = settings.isExperimentalFeatures ?? false;
this.tags = getTagFilters(tags);
this.dateAdapter.setLocale(settings.locale ?? DEFAULT_LOCALE);
this.changeDetectorRef.markForCheck();
});

67
apps/client/src/app/components/user-account-settings/user-account-settings.html

@ -5,14 +5,15 @@
<div class="d-flex py-1">
<div class="w-100">
<div
class="d-flex"
[class.mb-2]="!hasImpersonationId"
class="align-items-sm-baseline g-0 row"
[class.mb-3]="!hasImpersonationId"
[class.mb-4]="hasImpersonationId"
[class.mb-sm-2]="!hasImpersonationId"
>
<div class="align-items-center d-flex pe-1 pt-1 w-50">
<div class="col-sm-6 mb-1 mb-sm-0 pe-sm-1">
<ng-container i18n>Base Currency</ng-container>
</div>
<div class="ps-1 w-50" [formGroup]="baseCurrencyForm">
<div class="col-sm-6 ps-sm-1" [formGroup]="baseCurrencyForm">
<mat-form-field
appearance="outline"
class="w-100"
@ -31,11 +32,11 @@
</div>
</div>
@if (user?.settings?.isExperimentalFeatures) {
<div class="d-flex mb-2">
<div class="align-items-center d-flex pe-1 pt-1 w-50">
<div class="align-items-sm-baseline g-0 mb-3 mb-sm-2 row">
<div class="col-sm-6 mb-1 mb-sm-0 pe-sm-1">
<ng-container i18n>Performance Calculation</ng-container>
</div>
<div class="ps-1 w-50">
<div class="col-sm-6 ps-sm-1">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-select
name="performanceCalculationType"
@ -61,8 +62,8 @@
</div>
</div>
}
<div class="align-items-center d-flex mb-2">
<div class="pe-1 w-50">
<div class="align-items-sm-baseline g-0 mb-3 mb-sm-2 row">
<div class="col-sm-6 mb-1 mb-sm-0 pe-sm-1">
<div i18n>Language</div>
@if (isCommunityLanguage()) {
<div class="hint-text text-body-secondary" i18n>
@ -77,7 +78,7 @@
</div>
}
</div>
<div class="ps-1 w-50">
<div class="col-sm-6 ps-sm-1">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-select
name="language"
@ -156,14 +157,14 @@
</mat-form-field>
</div>
</div>
<div class="align-items-center d-flex mb-4">
<div class="pe-1 w-50">
<div class="align-items-sm-baseline g-0 mb-3 mb-sm-4 row">
<div class="col-sm-6 mb-1 mb-sm-0 pe-sm-1">
<div i18n>Locale</div>
<div class="hint-text text-body-secondary">
<div class="d-none d-sm-block hint-text text-body-secondary">
<ng-container i18n>Date and number format</ng-container>
</div>
</div>
<div class="ps-1 w-50">
<div class="col-sm-6 ps-sm-1">
<mat-form-field
appearance="outline"
class="w-100 with-value-hint"
@ -181,7 +182,7 @@
<mat-option [value]="locale">{{ locale }}</mat-option>
}
</mat-select>
<mat-hint class="d-flex mt-2 text-body-secondary text-truncate">
<mat-hint class="d-flex mt-2 text-body-secondary">
<gf-value
[deviceType]="deviceType()"
[isDate]="true"
@ -201,11 +202,11 @@
</mat-form-field>
</div>
</div>
<div class="d-flex">
<div class="align-items-center d-flex pe-1 pt-1 w-50">
<div class="align-items-sm-baseline g-0 row">
<div class="col-sm-6 mb-1 mb-sm-0 pe-sm-1">
<ng-container i18n>Appearance</ng-container>
</div>
<div class="ps-1 w-50">
<div class="col-sm-6 ps-sm-1">
<mat-form-field appearance="outline" class="w-100 without-hint">
<mat-select
class="with-placeholder-as-option"
@ -226,15 +227,15 @@
</div>
</div>
</div>
<div class="align-items-center d-flex mt-4 py-1">
<div class="pe-1 w-50">
<div class="align-items-baseline g-0 mt-4 py-1 row">
<div class="col col-sm-6 pe-1">
<div i18n>Presenter View</div>
<div class="hint-text text-body-secondary" i18n>
Protection for sensitive information like absolute performances and
quantity values
</div>
</div>
<div class="ps-1 w-50">
<div class="col-auto col-sm-6 ps-1">
<mat-slide-toggle
color="primary"
hideIcon="true"
@ -244,14 +245,14 @@
/>
</div>
</div>
<div class="d-flex mt-4 py-1">
<div class="pe-1 w-50">
<div class="align-items-baseline g-0 mt-4 py-1 row">
<div class="col col-sm-6 pe-1">
<div i18n>Zen Mode</div>
<div class="hint-text text-body-secondary" i18n>
Distraction-free experience for turbulent times
</div>
</div>
<div class="ps-1 w-50">
<div class="col-auto col-sm-6 ps-1">
<mat-slide-toggle
color="primary"
hideIcon="true"
@ -261,14 +262,14 @@
/>
</div>
</div>
<div class="align-items-center d-flex mt-4 py-1">
<div class="pe-1 w-50">
<div class="align-items-baseline g-0 mt-4 py-1 row">
<div class="col col-sm-6 pe-1">
<div i18n>Biometric Authentication</div>
<div class="hint-text text-body-secondary" i18n>
Sign in with fingerprint
</div>
</div>
<div class="ps-1 w-50">
<div class="col-auto col-sm-6 ps-1">
<mat-slide-toggle
color="primary"
hideIcon="true"
@ -280,14 +281,14 @@
/>
</div>
</div>
<div class="align-items-center d-flex mt-4 py-1">
<div class="pe-1 w-50">
<div class="align-items-baseline g-0 mt-4 py-1 row">
<div class="col col-sm-6 pe-1">
<div i18n>Experimental Features</div>
<div class="hint-text text-body-secondary" i18n>
Sneak peek at upcoming functionality
</div>
</div>
<div class="ps-1 w-50">
<div class="col-auto col-sm-6 ps-1">
<mat-slide-toggle
color="primary"
hideIcon="true"
@ -298,14 +299,14 @@
</div>
</div>
@if (deviceType() !== 'mobile' && hasPermissionToEnableGhostfolioAi) {
<div class="align-items-center d-flex mt-4 py-1">
<div class="pe-1 w-50">
<div class="align-items-baseline g-0 mt-4 py-1 row">
<div class="col col-sm-6 pe-1">
<div>Ghostfolio AI</div>
<div class="hint-text text-body-secondary" i18n>
Powered by third-party AI providers
</div>
</div>
<div class="ps-1 w-50">
<div class="col-auto col-sm-6 ps-1">
<mat-slide-toggle
color="primary"
hideIcon="true"

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

@ -6,10 +6,7 @@ import {
DEFAULT_LOCALE
} from '@ghostfolio/common/config';
import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos';
import {
getDateFormatString,
getStringOrNull
} from '@ghostfolio/common/helper';
import { getStringOrNull } from '@ghostfolio/common/helper';
import {
AssetClassSelectorOption,
LookupItem
@ -41,7 +38,6 @@ import {
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import {
MAT_DIALOG_DATA,
@ -102,7 +98,6 @@ export class GfCreateOrUpdateActivityDialogComponent {
protected currencies: string[] = [];
protected currencyOfAssetProfile: string | undefined;
protected currentMarketPrice: number | null = null;
protected defaultDateFormat: string;
protected defaultLookupItems: LookupItem[] = [];
protected hasPermissionToCreateOwnTag: boolean;
protected isLoading = false;
@ -119,7 +114,6 @@ export class GfCreateOrUpdateActivityDialogComponent {
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly dateAdapter = inject<DateAdapter<Date, string>>(DateAdapter);
private readonly destroyRef = inject(DestroyRef);
private readonly dialogRef =
inject<MatDialogRef<GfCreateOrUpdateActivityDialogComponent>>(MatDialogRef);
@ -127,7 +121,6 @@ export class GfCreateOrUpdateActivityDialogComponent {
private readonly impersonationStorageService = inject(
ImpersonationStorageService
);
private locale = inject<string>(MAT_DATE_LOCALE);
private readonly userService = inject(UserService);
public constructor() {
@ -143,15 +136,11 @@ export class GfCreateOrUpdateActivityDialogComponent {
!this.impersonationStorageService.getId() &&
hasPermission(this.data.user?.permissions, permissions.createOwnTag);
this.locale = this.data.user.settings.locale ?? DEFAULT_LOCALE;
this.mode = this.data.activity?.id ? 'update' : 'create';
this.dateAdapter.setLocale(this.locale);
const { currencies } = this.dataService.fetchInfo();
this.currencies = currencies;
this.defaultDateFormat = getDateFormatString(this.locale);
this.dataService
.fetchPortfolioHoldings({

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

@ -76,6 +76,7 @@ import type { LivenessHealthResponse } from './responses/liveness-health-respons
import type { LookupResponse } from './responses/lookup-response.interface';
import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface';
import type { OAuthResponse } from './responses/oauth-response.interface';
import type { PlatformResponse } from './responses/platform-response.interface';
import type { PlatformsResponse } from './responses/platforms-response.interface';
import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface';
import type { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface';
@ -172,6 +173,7 @@ export {
MarketDataOfMarketsResponse,
NullableLineChartItem,
OAuthResponse,
PlatformResponse,
PlatformsResponse,
PortfolioChart,
PortfolioDetails,

6
libs/common/src/lib/interfaces/responses/platform-response.interface.ts

@ -0,0 +1,6 @@
import type { Platform } from '@ghostfolio/prisma/browser';
export interface PlatformResponse extends Pick<
Platform,
'id' | 'name' | 'url'
> {}

4
libs/common/src/lib/interfaces/responses/platforms-response.interface.ts

@ -1,5 +1,5 @@
import type { Platform } from '@ghostfolio/prisma/browser';
import type { PlatformResponse } from './platform-response.interface';
export interface PlatformsResponse {
platforms: Platform[];
platforms: (PlatformResponse & { accountCount?: number })[];
}

9
libs/ui/src/lib/account-balances/account-balances.component.ts

@ -11,7 +11,6 @@ import {
Component,
EventEmitter,
OnChanges,
OnInit,
Output,
effect,
inject,
@ -25,7 +24,6 @@ import {
ReactiveFormsModule
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { DateAdapter } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
@ -63,7 +61,7 @@ import { GfValueComponent } from '../value';
styleUrls: ['./account-balances.component.scss'],
templateUrl: './account-balances.component.html'
})
export class GfAccountBalancesComponent implements OnChanges, OnInit {
export class GfAccountBalancesComponent implements OnChanges {
@Output() accountBalanceCreated = new EventEmitter<CreateAccountBalanceDto>();
@Output() accountBalanceDeleted = new EventEmitter<string>();
@ -94,7 +92,6 @@ export class GfAccountBalancesComponent implements OnChanges, OnInit {
>();
public maxDate = new Date();
private dateAdapter = inject<DateAdapter<Date, string>>(DateAdapter);
private notificationService = inject(NotificationService);
public constructor() {
@ -112,10 +109,6 @@ export class GfAccountBalancesComponent implements OnChanges, OnInit {
});
}
public ngOnInit() {
this.dateAdapter.setLocale(this.locale());
}
public ngOnChanges() {
if (this.accountBalances()) {
this.dataSource = new MatTableDataSource(this.accountBalances());

13
libs/ui/src/lib/file-drop-area/file-drop-area.component.html

@ -1,13 +1,14 @@
@if (isLoading()) {
<div
class="align-items-center d-flex drop-area flex-column justify-content-center p-4 text-body-secondary text-center"
>
<mat-spinner [diameter]="20" />
<span>{{ loadingText() }}</span>
<div class="aura" role="progressbar" [attr.aria-label]="loadingText()">
<div
class="align-items-center d-flex drop-area flex-column justify-content-center rounded text-body-secondary text-center"
>
<span>{{ loadingText() }}</span>
</div>
</div>
} @else {
<button
class="drop-area p-4 text-body-secondary text-center"
class="drop-area rounded text-body-secondary text-center"
type="button"
(click)="onSelectFile()"
(dragenter)="onDragEnter($event)"

94
libs/ui/src/lib/file-drop-area/file-drop-area.component.scss

@ -1,10 +1,71 @@
@use '@angular/material' as mat;
@property --aura-angle {
inherits: true;
initial-value: 0deg;
syntax: '<angle>';
}
:host {
--aura-padding: 0.125rem;
--cloud-icon-size: 2.5rem;
--drop-area-padding: 1.5rem;
a {
color: rgba(var(--palette-primary-500), 1);
}
// Based on the aura component of daisyUI (https://daisyui.com/components/aura)
.aura {
--aura-duration: 4000ms;
animation: aura var(--aura-duration) linear infinite;
background-image: repeating-conic-gradient(
from var(--aura-angle),
transparent 0%,
transparent 40%,
rgba(var(--palette-primary-500), 1) 50%
);
border-radius: calc(var(--bs-border-radius) + var(--aura-padding));
padding: var(--aura-padding);
position: relative;
&::after,
&::before {
animation: inherit;
background-image: inherit;
border-radius: inherit;
content: '';
inset: 0;
position: absolute;
}
&::after {
filter: blur(1rem);
opacity: 0.3;
}
&::before {
filter: blur(0.25rem);
opacity: 0.7;
}
.drop-area {
background-color: var(--light-background);
border-style: solid;
margin: 0;
// Keep the height of the drop area with the cloud icon
padding-block: calc(
var(--drop-area-padding) + (var(--cloud-icon-size) / 2)
);
pointer-events: none;
position: relative;
z-index: 1;
}
@media (prefers-reduced-motion: reduce) {
animation: none;
}
}
.drop-area {
background-color: rgba(var(--palette-foreground-base), 0.02);
border: 1px dashed
@ -12,7 +73,9 @@
var(--palette-foreground-divider),
var(--palette-foreground-divider-alpha)
);
border-radius: 0.25rem;
// Reserve the space of the aura
margin: var(--aura-padding);
padding: var(--drop-area-padding);
&:hover {
border-color: rgba(var(--palette-primary-500), 1) !important;
@ -20,22 +83,18 @@
}
.cloud-icon {
font-size: 2.5rem;
}
.mat-mdc-progress-spinner {
@include mat.progress-spinner-overrides(
(
active-indicator-color: var(--gf-theme-primary-500)
)
);
margin: 0.625rem 0;
font-size: var(--cloud-icon-size);
}
}
}
:host-context(.theme-dark) {
.aura {
.drop-area {
background-color: var(--dark-background);
}
}
.drop-area {
border-color: rgba(
var(--palette-foreground-divider-dark),
@ -43,3 +102,12 @@
);
}
}
@keyframes aura {
to {
--aura-angle: 360deg;
// Force the GPU acceleration to prevent lags in Safari
transform: translateZ(1px);
}
}

6
libs/ui/src/lib/file-drop-area/file-drop-area.component.stories.ts

@ -47,9 +47,3 @@ export const Loading: Story = {
isLoading: true
}
};
export const WithGhostfolioAi: Story = {
args: {
fileFormats: FILE_FORMATS_WITH_GHOSTFOLIO_AI
}
};

3
libs/ui/src/lib/file-drop-area/file-drop-area.component.ts

@ -7,7 +7,6 @@ import {
input,
output
} from '@angular/core';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { IonIcon } from '@ionic/angular/standalone';
import { addIcons } from 'ionicons';
import { cloudUploadOutline } from 'ionicons/icons';
@ -20,7 +19,7 @@ import { FileFormat } from './interfaces/interfaces';
'(document:paste)': 'onPaste($event)',
class: 'd-flex flex-column'
},
imports: [IonIcon, MatProgressSpinnerModule],
imports: [IonIcon],
selector: 'gf-file-drop-area',
styleUrls: ['./file-drop-area.component.scss'],
templateUrl: './file-drop-area.component.html'

11
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts

@ -6,7 +6,6 @@ import {
Component,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
OnInit,
inject
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -17,7 +16,6 @@ import {
Validators
} from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
import { MatDatepickerModule } from '@angular/material/datepicker';
import {
MAT_DIALOG_DATA,
@ -50,7 +48,7 @@ import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces'
styleUrls: ['./historical-market-data-editor-dialog.scss'],
templateUrl: 'historical-market-data-editor-dialog.html'
})
export class GfHistoricalMarketDataEditorDialogComponent implements OnInit {
export class GfHistoricalMarketDataEditorDialogComponent {
public readonly data =
inject<HistoricalMarketDataEditorDialogParams>(MAT_DIALOG_DATA);
@ -60,23 +58,16 @@ export class GfHistoricalMarketDataEditorDialogComponent implements OnInit {
});
private readonly destroyRef = inject(DestroyRef);
private readonly locale =
this.data.user.settings.locale ?? inject<string>(MAT_DATE_LOCALE);
public constructor(
private adminService: AdminService,
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private dateAdapter: DateAdapter<Date, string>,
public dialogRef: MatDialogRef<GfHistoricalMarketDataEditorDialogComponent>
) {
addIcons({ calendarClearOutline, refreshOutline });
}
public ngOnInit() {
this.dateAdapter.setLocale(this.locale);
}
public onCancel() {
this.dialogRef.close({ withRefresh: false });
}

2
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts

@ -1,4 +1,3 @@
import { User } from '@ghostfolio/common/interfaces';
import { DataSource } from '@ghostfolio/prisma/enums';
export interface HistoricalMarketDataEditorDialogParams {
@ -7,5 +6,4 @@ export interface HistoricalMarketDataEditorDialogParams {
dateString: string;
marketPrice?: number;
symbol: string;
user: User;
}

6
libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts

@ -4,7 +4,7 @@ import {
getDateFormatString,
getLocale
} from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { LineChartItem } from '@ghostfolio/common/interfaces';
import type { MarketData } from '@ghostfolio/prisma/browser';
import { DataSource } from '@ghostfolio/prisma/enums';
import { DataService } from '@ghostfolio/ui/services';
@ -70,7 +70,6 @@ export class GfHistoricalMarketDataEditorComponent
@Input() dataSource: DataSource;
@Input() dateOfFirstActivity?: Date;
@Input() symbol: string;
@Input() user: User;
@Output() marketDataChanged = new EventEmitter<boolean>();
@ -233,8 +232,7 @@ export class GfHistoricalMarketDataEditorComponent
currency: this.currency,
dataSource: this.dataSource,
dateString: `${yearMonth}-${day}`,
symbol: this.symbol,
user: this.user
symbol: this.symbol
},
height: this.deviceType() === 'mobile' ? '98vh' : '80vh',
width: this.deviceType() === 'mobile' ? '100vw' : '50rem'

29
libs/ui/src/lib/services/admin.service.ts

@ -5,10 +5,8 @@ import {
} from '@ghostfolio/common/config';
import {
CreateAssetProfileSplitDto,
CreatePlatformDto,
MergeAssetProfileDto,
UpdateAssetProfileDto,
UpdatePlatformDto
UpdateAssetProfileDto
} from '@ghostfolio/common/dtos';
import {
AdminData,
@ -21,11 +19,7 @@ import {
EnhancedAssetProfile
} from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import type {
AssetProfileSplit,
MarketData,
Platform
} from '@ghostfolio/prisma/browser';
import type { AssetProfileSplit, MarketData } from '@ghostfolio/prisma/browser';
import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
@ -61,10 +55,6 @@ export class AdminService {
});
}
public deletePlatform(aId: string) {
return this.http.delete<void>(`/api/v1/platform/${aId}`);
}
public deleteAssetProfileSplit({
dataSource,
id,
@ -113,10 +103,6 @@ export class AdminService {
});
}
public fetchPlatforms() {
return this.http.get<Platform[]>('/api/v1/platform');
}
public fetchUserById(id: string) {
return this.http.get<AdminUserResponse>(`/api/v1/admin/user/${id}`);
}
@ -252,17 +238,6 @@ export class AdminService {
);
}
public postPlatform(aPlatform: CreatePlatformDto) {
return this.http.post<Platform>(`/api/v1/platform`, aPlatform);
}
public putPlatform(aPlatform: UpdatePlatformDto) {
return this.http.put<Platform>(
`/api/v1/platform/${aPlatform.id}`,
aPlatform
);
}
public syncDemoUserAccount() {
return this.http.get<void>(`/api/v1/admin/demo-user/sync`);
}

18
libs/ui/src/lib/services/data.service.ts

@ -3,6 +3,7 @@ import {
CreateAccountBalanceDto,
CreateAccountDto,
CreateOrderDto,
CreatePlatformDto,
CreateTagDto,
CreateUserDto,
CreateWatchlistItemDto,
@ -13,6 +14,7 @@ import {
UpdateBulkMarketDataDto,
UpdateOrderDto,
UpdateOwnAccessTokenDto,
UpdatePlatformDto,
UpdatePropertyDto,
UpdateTagDto,
UpdateUserSettingDto
@ -46,6 +48,7 @@ import {
LookupResponse,
MarketDataOfMarketsResponse,
OAuthResponse,
PlatformResponse,
PlatformsResponse,
PortfolioDetails,
PortfolioDividendsResponse,
@ -366,6 +369,10 @@ export class DataService {
return this.http.delete<UserModel>(`/api/v1/user`, { body: aData });
}
public deletePlatform(aId: string) {
return this.http.delete<void>(`/api/v1/platforms/${aId}`);
}
public deleteTag(aId: string) {
return this.http.delete<Tag>(`/api/v1/tags/${aId}`);
}
@ -914,6 +921,10 @@ export class DataService {
return this.http.post<MarketData>(url, marketData);
}
public postPlatform(aPlatform: CreatePlatformDto) {
return this.http.post<PlatformResponse>(`/api/v1/platforms`, aPlatform);
}
public postTag(aTag: CreateTagDto) {
return this.http.post<Tag>(`/api/v1/tags`, aTag);
}
@ -953,6 +964,13 @@ export class DataService {
);
}
public putPlatform(aPlatform: UpdatePlatformDto) {
return this.http.put<PlatformResponse>(
`/api/v1/platforms/${aPlatform.id}`,
aPlatform
);
}
public putTag(aTag: UpdateTagDto) {
return this.http.put<Tag>(`/api/v1/tags/${aTag.id}`, aTag);
}

47
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "3.88.0",
"version": "3.89.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "3.88.0",
"version": "3.89.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@ -29,9 +29,9 @@
"@internationalized/number": "3.6.8",
"@ionic/angular": "8.8.12",
"@keyv/redis": "5.1.6",
"@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"@modelcontextprotocol/core": "2.3.1",
"@modelcontextprotocol/node": "2.1.1",
"@modelcontextprotocol/server": "2.3.1",
"@nest-lab/throttler-storage-redis": "1.2.0",
"@nestjs/bull": "11.0.5",
"@nestjs/cache-manager": "3.1.3",
@ -9043,10 +9043,10 @@
}
},
"node_modules/@modelcontextprotocol/core": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz",
"integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==",
"license": "MIT",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.3.1.tgz",
"integrity": "sha512-laVmIhPGpWi7rG9q+0UigblRN0Hws/Yjlb6qLFtVqryXVLkc4h+yN8z731POD2LnAGQlFe7/tXhUm7Bi0YB91g==",
"license": "Apache-2.0",
"dependencies": {
"zod": "^4.2.0"
},
@ -9055,24 +9055,19 @@
}
},
"node_modules/@modelcontextprotocol/node": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.0.0.tgz",
"integrity": "sha512-Y4hAC2XdGDUdDOCbLDOCA4+aL3NUldjsOWlDL/YwpAxrPhRm1xHd7lZ+mLacvZ9t3PaH28wgNoaLQGrIk1P2pg==",
"license": "MIT",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/node/-/node-2.1.1.tgz",
"integrity": "sha512-EY92OAXN2xYL7tNS/MVsWJW1v8VHAPwl9B4Ca3pCrjmvqyC+S1C/6UHsMxF2GoFPraS5Yyg56vFz9brVpFojMA==",
"license": "Apache-2.0",
"dependencies": {
"@hono/node-server": "^1.19.9"
"@hono/node-server": "^1.19.9",
"hono": "^4.11.4"
},
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@modelcontextprotocol/server": "^2.0.0",
"hono": "^4.11.4"
},
"peerDependenciesMeta": {
"hono": {
"optional": true
}
"@modelcontextprotocol/server": "^2.3.0"
}
},
"node_modules/@modelcontextprotocol/sdk": {
@ -9116,12 +9111,12 @@
}
},
"node_modules/@modelcontextprotocol/server": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz",
"integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==",
"license": "MIT",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.3.1.tgz",
"integrity": "sha512-e59MfWuSssj6DoQ96JCTqdW1AXT5/XtnTl0WWd6MU1Ka434fFUGGosCLJbGNySDhHGvxQWoRWK137FXYFkYNWw==",
"license": "Apache-2.0",
"dependencies": {
"@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/core": "2.3.1",
"zod": "^4.2.0"
},
"engines": {

8
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "3.88.0",
"version": "3.89.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
@ -73,9 +73,9 @@
"@internationalized/number": "3.6.8",
"@ionic/angular": "8.8.12",
"@keyv/redis": "5.1.6",
"@modelcontextprotocol/core": "2.0.0",
"@modelcontextprotocol/node": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"@modelcontextprotocol/core": "2.3.1",
"@modelcontextprotocol/node": "2.1.1",
"@modelcontextprotocol/server": "2.3.1",
"@nest-lab/throttler-storage-redis": "1.2.0",
"@nestjs/bull": "11.0.5",
"@nestjs/cache-manager": "3.1.3",

Loading…
Cancel
Save