) {
addIcons({ calendarClearOutline, refreshOutline });
}
- public ngOnInit() {
- this.dateAdapter.setLocale(this.locale);
- }
-
public onCancel() {
this.dialogRef.close({ withRefresh: false });
}
From 892c0e0aab65182de184419b74c36c0186049074 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 08:47:31 +0200
Subject: [PATCH 06/18] Bugfix/missing tooltip of client of last request in
users table (#8230)
* Fix missing tooltip
* Update changelog
---
CHANGELOG.md | 1 +
apps/client/src/app/components/admin-users/admin-users.html | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c31fecf7b9..73207ee4ce 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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
diff --git a/apps/client/src/app/components/admin-users/admin-users.html b/apps/client/src/app/components/admin-users/admin-users.html
index 7b49dc5f60..98056f00f7 100644
--- a/apps/client/src/app/components/admin-users/admin-users.html
+++ b/apps/client/src/app/components/admin-users/admin-users.html
@@ -199,7 +199,7 @@
@if (element.lastRequestClient) {
From 7e54ba65929de0ef639830e2139366f96b600602 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 09:46:13 +0200
Subject: [PATCH 07/18] Task/extend app module spec by route snapshot (#8227)
Extend app module spec by route snapshot
---
.../app/__snapshots__/app.module.spec.ts.snap | 1183 +++++++++++++++++
apps/api/src/app/app.module.spec.ts | 212 ++-
2 files changed, 1372 insertions(+), 23 deletions(-)
create mode 100644 apps/api/src/app/__snapshots__/app.module.spec.ts.snap
diff --git a/apps/api/src/app/__snapshots__/app.module.spec.ts.snap b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
new file mode 100644
index 0000000000..34cdbb907a
--- /dev/null
+++ b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
@@ -0,0 +1,1183 @@
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
+
+exports[`AppModule should keep the security of each route 1`] = `
+{
+ "DELETE /access/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deleteAccess",
+ },
+ "DELETE /account-balance/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "deleteAccountBalance",
+ "scopes": [
+ "account:update",
+ ],
+ },
+ "DELETE /account/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "deleteAccount",
+ "scopes": [
+ "account:delete",
+ ],
+ },
+ "DELETE /activities": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "deleteActivity",
+ "scopes": [
+ "activity:delete",
+ ],
+ },
+ "DELETE /activities/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "deleteActivity",
+ "scopes": [
+ "activity:delete",
+ ],
+ },
+ "DELETE /admin/profile-data/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "DELETE /admin/queue/job": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "DELETE /admin/queue/job/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "DELETE /asset-profiles/:dataSource/:symbol/splits/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "DELETE /auth-device/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deleteAuthDevice",
+ },
+ "DELETE /benchmarks/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "DELETE /platform/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deletePlatform",
+ },
+ "DELETE /tags/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deleteTag",
+ },
+ "DELETE /user": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ ],
+ "interceptors": [],
+ "permission": "deleteOwnUser",
+ },
+ "DELETE /user/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deleteUser",
+ },
+ "DELETE /watchlist/:dataSource/:symbol": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "deleteWatchlistItem",
+ "scopes": [
+ "watchlist:delete",
+ ],
+ },
+ "GET /access": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "GET /account": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "account:read",
+ ],
+ },
+ "GET /account/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "account:read",
+ ],
+ },
+ "GET /account/:id/balances": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "account:read",
+ ],
+ },
+ "GET /activities": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "activity:read",
+ ],
+ },
+ "GET /activities/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "activity:read",
+ ],
+ },
+ "GET /admin": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "GET /admin/demo-user/sync": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "syncDemoUserAccount",
+ },
+ "GET /admin/queue/job": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "GET /admin/queue/job/:id/execute": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "GET /admin/user": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "GET /admin/user/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "GET /ai/prompt/:mode": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "readAiPrompt",
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /asset-profiles": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "GET /asset-profiles/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /asset/:dataSource/:symbol": {
+ "guards": [],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /assets/:languageCode/site.webmanifest (version neutral)": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /auth/google": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('google')",
+ ],
+ "interceptors": [],
+ },
+ "GET /auth/google/callback (version neutral)": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "OAuthCallbackGuardMixin('google')",
+ ],
+ "interceptors": [],
+ },
+ "GET /auth/oidc (version neutral)": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('oidc')",
+ ],
+ "interceptors": [],
+ },
+ "GET /auth/oidc/callback (version neutral)": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "OAuthCallbackGuardMixin('oidc')",
+ ],
+ "interceptors": [],
+ },
+ "GET /auth/webauthn/generate-registration-options": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "GET /benchmarks": {
+ "allowDuringImpersonation": true,
+ "guards": [],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /benchmarks/:dataSource/:symbol/:startDateString": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /data-providers/ghostfolio/asset-profile/:symbol": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /data-providers/ghostfolio/dividends/:symbol (version 2)": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /data-providers/ghostfolio/historical/:symbol (version 2)": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /data-providers/ghostfolio/lookup (version 2)": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /data-providers/ghostfolio/markets": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /data-providers/ghostfolio/quotes (version 2)": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /data-providers/ghostfolio/status (version 2)": {
+ "guards": [
+ "AuthGuard('api-key')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "enableDataProviderGhostfolio",
+ },
+ "GET /exchange-rate/:symbol/:dateString": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "GET /export": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /health": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /health/ai": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /health/data-enhancer/:name": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /health/data-provider/:dataSource": {
+ "guards": [],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /health/liveness": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /import/dividends/:dataSource/:symbol": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /info": {
+ "guards": [],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ ],
+ },
+ "GET /logo": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /logo/:dataSource/:symbol": {
+ "guards": [],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /market-data/markets": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "readMarketDataOfMarkets",
+ },
+ "GET /platform": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "readPlatformsWithAccountCount",
+ },
+ "GET /platforms": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "readPlatforms",
+ },
+ "GET /portfolio/details": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /portfolio/dividends": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /portfolio/holding/:dataSource/:symbol": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /portfolio/holdings": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /portfolio/investments": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /portfolio/performance (version 2)": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ "PerformanceLoggingInterceptor",
+ ],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /portfolio/report": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [],
+ "scopes": [
+ "portfolio:read",
+ ],
+ },
+ "GET /public/:accessId/portfolio": {
+ "guards": [],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ },
+ "GET /sitemap.xml (version neutral)": {
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /subscription/stripe/callback": {
+ "allowDuringImpersonation": true,
+ "guards": [],
+ "interceptors": [],
+ },
+ "GET /symbol/:dataSource/:symbol": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "GET /symbol/:dataSource/:symbol/:dateString": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "GET /symbol/lookup": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ ],
+ },
+ "GET /tags": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "readTags",
+ },
+ "GET /user": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ },
+ "GET /watchlist": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ ],
+ "permission": "readWatchlist",
+ "scopes": [
+ "watchlist:read",
+ ],
+ },
+ "PATCH /admin/profile-data/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "PATCH /asset-profiles/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /access": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "createAccess",
+ },
+ "POST /account": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "createAccount",
+ "scopes": [
+ "account:create",
+ ],
+ },
+ "POST /account-balance": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "createAccountBalance",
+ "scopes": [
+ "account:update",
+ ],
+ },
+ "POST /account/transfer-balance": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [],
+ "permission": "updateAccount",
+ "scopes": [
+ "account:update",
+ ],
+ },
+ "POST /activities": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "createActivity",
+ "scopes": [
+ "activity:create",
+ ],
+ },
+ "POST /admin/gather": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/gather/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/gather/:dataSource/:symbol/:dateString": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/gather/max": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/gather/profile-data": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/gather/profile-data/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/market-data/:dataSource/:symbol/test": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/profile-data/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "accessAdminControl",
+ },
+ "POST /admin/profile-data/:dataSource/:symbol/merge": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "accessAdminControl",
+ },
+ "POST /ai/activities": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "CustomThrottlerGuard",
+ ],
+ "interceptors": [
+ "MixinInterceptor",
+ ],
+ "permission": "enableGhostfolioAi",
+ },
+ "POST /api-keys": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "createApiKey",
+ },
+ "POST /asset-profiles/:dataSource/:symbol/splits": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ },
+ "POST /auth/anonymous": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "CustomThrottlerGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /auth/webauthn/generate-authentication-options": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "CustomThrottlerGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /auth/webauthn/verify-attestation": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /auth/webauthn/verify-authentication": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "CustomThrottlerGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /benchmarks": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /cache/flush": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /import": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "createActivity",
+ },
+ "POST /market-data/:dataSource/:symbol": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ ],
+ "interceptors": [],
+ },
+ "POST /platform": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "createPlatform",
+ },
+ "POST /subscription/redeem-coupon": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "CustomThrottlerGuard",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /subscription/stripe/checkout-session": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "CustomThrottlerGuard",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /tags": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ ],
+ "interceptors": [],
+ },
+ "POST /user": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "CustomThrottlerGuard",
+ ],
+ "interceptors": [],
+ },
+ "POST /user/:id/access-token": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "POST /user/access-token": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "updateOwnAccessToken",
+ },
+ "POST /watchlist": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "createWatchlistItem",
+ "scopes": [
+ "watchlist:create",
+ ],
+ },
+ "PUT /access/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "updateAccess",
+ },
+ "PUT /account/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "updateAccount",
+ "scopes": [
+ "account:update",
+ ],
+ },
+ "PUT /activities/:id": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ "RedactValuesInResponseInterceptor",
+ ],
+ "permission": "updateActivity",
+ "scopes": [
+ "activity:update",
+ ],
+ },
+ "PUT /admin/settings/:key": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "accessAdminControl",
+ },
+ "PUT /platform/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "updatePlatform",
+ },
+ "PUT /portfolio/holding/:dataSource/:symbol/tags": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ "ScopeGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInRequestInterceptor",
+ ],
+ "permission": "updateActivity",
+ "scopes": [
+ "activity:update",
+ ],
+ },
+ "PUT /tags/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "updateTag",
+ },
+ "PUT /user/setting": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ "ImpersonationGuard",
+ ],
+ "interceptors": [
+ "TransformDataSourceInResponseInterceptor",
+ ],
+ },
+}
+`;
diff --git a/apps/api/src/app/app.module.spec.ts b/apps/api/src/app/app.module.spec.ts
index 976e08393b..f04485ccdd 100644
--- a/apps/api/src/app/app.module.spec.ts
+++ b/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 | 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(
+ 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;
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(
+ 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(
+ 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(
+ 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();
+ });
});
From 6157d2bd53932d049c0afbd6960fe8d87a496e83 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 12:02:52 +0200
Subject: [PATCH 08/18] Task/remove unused user in historical market data
editor (#8243)
Remove unused user
---
.../asset-profile-dialog/asset-profile-dialog.html | 1 -
.../holding-detail-dialog/holding-detail-dialog.html | 1 -
.../interfaces/interfaces.ts | 2 --
.../historical-market-data-editor.component.ts | 6 ++----
4 files changed, 2 insertions(+), 8 deletions(-)
diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
index 0b9e038a02..8e6341a709 100644
--- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.html
+++ b/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)"
/>
diff --git a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html b/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
index efb693e59a..de56a7b003 100644
--- a/apps/client/src/app/components/holding-detail-dialog/holding-detail-dialog.html
+++ b/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)"
/>
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
index 4a0e419e7b..4803071ac7 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/interfaces/interfaces.ts
+++ b/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;
}
diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
index 94baafbf1a..cefc96b8b2 100644
--- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor.component.ts
+++ b/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();
@@ -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'
From 62b1ea937fdcd2775ebd66e2af3980ef9a9b8948 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 19:25:12 +0200
Subject: [PATCH 09/18] Task/harmonize pointer events override of ion icon
(#8254)
Harmonize pointer events override
---
.../portfolio-performance.component.html | 1 +
.../portfolio-performance.component.scss | 4 ----
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html
index 9be2be344e..c458080e3c 100644
--- a/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html
+++ b/apps/client/src/app/components/portfolio-performance/portfolio-performance.component.html
@@ -3,6 +3,7 @@
@if ((errors()?.length ?? 0) > 0 && !isLoading()) {
Date: Tue, 20 Oct 2026 19:26:21 +0200
Subject: [PATCH 10/18] Task/improve loading state of file drop area component
(#8250)
* Improve loading state
* Update changelog
---
CHANGELOG.md | 1 +
.../file-drop-area.component.html | 13 +--
.../file-drop-area.component.scss | 94 ++++++++++++++++---
.../file-drop-area.component.stories.ts | 6 --
.../file-drop-area.component.ts | 3 +-
5 files changed, 90 insertions(+), 27 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73207ee4ce..11cae3d5e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- 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
diff --git a/libs/ui/src/lib/file-drop-area/file-drop-area.component.html b/libs/ui/src/lib/file-drop-area/file-drop-area.component.html
index b5c6e19021..a4d1d6c91e 100644
--- a/libs/ui/src/lib/file-drop-area/file-drop-area.component.html
+++ b/libs/ui/src/lib/file-drop-area/file-drop-area.component.html
@@ -1,13 +1,14 @@
@if (isLoading()) {
-
-
-
{{ loadingText() }}
+
+
+ {{ loadingText() }}
+
} @else {
';
+}
: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);
+ }
+}
diff --git a/libs/ui/src/lib/file-drop-area/file-drop-area.component.stories.ts b/libs/ui/src/lib/file-drop-area/file-drop-area.component.stories.ts
index eeee334b07..5d14f276ab 100644
--- a/libs/ui/src/lib/file-drop-area/file-drop-area.component.stories.ts
+++ b/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
- }
-};
diff --git a/libs/ui/src/lib/file-drop-area/file-drop-area.component.ts b/libs/ui/src/lib/file-drop-area/file-drop-area.component.ts
index f63730e533..d6cc328b64 100644
--- a/libs/ui/src/lib/file-drop-area/file-drop-area.component.ts
+++ b/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'
From a03c4c9b26270744a06fc8aa025e22884fbaef65 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 19:27:11 +0200
Subject: [PATCH 11/18] Task/move redis cache module to services (#8226)
Move redis cache module to services
---
apps/api/src/app/activities/activities.module.ts | 2 +-
apps/api/src/app/app.module.ts | 2 +-
apps/api/src/app/auth/auth.module.ts | 2 +-
apps/api/src/app/auth/web-auth.service.ts | 2 +-
apps/api/src/app/cache/cache.controller.ts | 2 +-
apps/api/src/app/cache/cache.module.ts | 2 +-
apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts | 2 +-
.../data-providers/ghostfolio/ghostfolio.module.ts | 2 +-
apps/api/src/app/endpoints/public/public.module.ts | 2 +-
apps/api/src/app/health/health.module.ts | 2 +-
apps/api/src/app/health/health.service.ts | 2 +-
apps/api/src/app/import/import.module.ts | 2 +-
apps/api/src/app/info/info.module.ts | 2 +-
apps/api/src/app/info/info.service.ts | 2 +-
.../portfolio/calculator/portfolio-calculator.factory.ts | 2 +-
.../src/app/portfolio/calculator/portfolio-calculator.ts | 2 +-
.../roai/portfolio-calculator-baln-buy-and-buy.spec.ts | 6 +++---
...o-calculator-baln-buy-and-sell-in-two-activities.spec.ts | 6 +++---
.../roai/portfolio-calculator-baln-buy-and-sell.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-baln-buy.spec.ts | 6 +++---
...portfolio-calculator-btceur-in-base-currency-eur.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-btceur.spec.ts | 6 +++---
...rtfolio-calculator-btcusd-buy-and-sell-partially.spec.ts | 6 +++---
...-calculator-btcusd-buy-before-first-market-price.spec.ts | 6 +++---
.../roai/portfolio-calculator-btcusd-short.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-btcusd.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-cash.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-fee.spec.ts | 6 +++---
.../portfolio-calculator-googl-buy-with-dividend.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-googl-buy.spec.ts | 6 +++---
.../portfolio-calculator-googl-fee-and-dividend.spec.ts | 6 +++---
...io-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-liability.spec.ts | 6 +++---
...-calculator-manual-buy-before-first-market-price.spec.ts | 6 +++---
...manual-buy-with-market-price-between-chart-dates.spec.ts | 6 +++---
.../roai/portfolio-calculator-msft-buy-and-sell.spec.ts | 6 +++---
...tfolio-calculator-msft-buy-from-two-data-sources.spec.ts | 6 +++---
.../portfolio-calculator-msft-buy-with-dividend.spec.ts | 6 +++---
.../roai/portfolio-calculator-no-activities.spec.ts | 6 +++---
...portfolio-calculator-novn-buy-and-sell-partially.spec.ts | 6 +++---
.../roai/portfolio-calculator-novn-buy-and-sell.spec.ts | 6 +++---
.../roai/portfolio-calculator-stock-split.spec.ts | 6 +++---
.../calculator/roai/portfolio-calculator-valuable.spec.ts | 6 +++---
.../portfolio/calculator/roai/portfolio-calculator.spec.ts | 6 +++---
.../roi/portfolio-calculator-baln-buy-and-buy.spec.ts | 6 +++---
.../roi/portfolio-calculator-baln-buy-and-sell.spec.ts | 6 +++---
.../calculator/roi/portfolio-calculator-baln-buy.spec.ts | 6 +++---
...rtfolio-calculator-baln-sell-and-buy-on-same-day.spec.ts | 6 +++---
...rtfolio-calculator-btcusd-buy-and-sell-partially.spec.ts | 6 +++---
.../calculator/roi/portfolio-calculator-cash.spec.ts | 6 +++---
.../roi/portfolio-calculator-msft-buy-with-dividend.spec.ts | 6 +++---
apps/api/src/app/portfolio/portfolio.module.ts | 2 +-
apps/api/src/events/events.module.ts | 2 +-
apps/api/src/events/portfolio-changed.listener.ts | 2 +-
apps/api/src/services/benchmark/benchmark.module.ts | 2 +-
apps/api/src/services/benchmark/benchmark.service.ts | 2 +-
apps/api/src/services/data-provider/data-provider.module.ts | 2 +-
.../services/data-provider/data-provider.service.spec.ts | 2 +-
.../api/src/services/data-provider/data-provider.service.ts | 2 +-
.../queues/portfolio-snapshot/portfolio-snapshot.module.ts | 2 +-
.../portfolio-snapshot/portfolio-snapshot.processor.ts | 2 +-
.../portfolio-snapshot/portfolio-snapshot.service.mock.ts | 2 +-
.../src/{app => services}/redis-cache/redis-cache.module.ts | 0
.../redis-cache/redis-cache.service.mock.ts | 0
.../{app => services}/redis-cache/redis-cache.service.ts | 0
65 files changed, 132 insertions(+), 132 deletions(-)
rename apps/api/src/{app => services}/redis-cache/redis-cache.module.ts (100%)
rename apps/api/src/{app => services}/redis-cache/redis-cache.service.mock.ts (100%)
rename apps/api/src/{app => services}/redis-cache/redis-cache.service.ts (100%)
diff --git a/apps/api/src/app/activities/activities.module.ts b/apps/api/src/app/activities/activities.module.ts
index 1508917f04..869a4d3650 100644
--- a/apps/api/src/app/activities/activities.module.ts
+++ b/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';
diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts
index 33617602d6..27fdff6b64 100644
--- a/apps/api/src/app/app.module.ts
+++ b/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,
@@ -64,7 +65,6 @@ 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';
diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts
index 879463cc13..1eafdb6091 100644
--- a/apps/api/src/app/auth/auth.module.ts
+++ b/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';
diff --git a/apps/api/src/app/auth/web-auth.service.ts b/apps/api/src/app/auth/web-auth.service.ts
index 40a86398a6..fe9a9467b2 100644
--- a/apps/api/src/app/auth/web-auth.service.ts
+++ b/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,
diff --git a/apps/api/src/app/cache/cache.controller.ts b/apps/api/src/app/cache/cache.controller.ts
index 680e7e6825..1072550d64 100644
--- a/apps/api/src/app/cache/cache.controller.ts
+++ b/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';
diff --git a/apps/api/src/app/cache/cache.module.ts b/apps/api/src/app/cache/cache.module.ts
index d435c72a64..6bc9fb16c3 100644
--- a/apps/api/src/app/cache/cache.module.ts
+++ b/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';
diff --git a/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts b/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts
index 3c540c3370..29dc7190d0 100644
--- a/apps/api/src/app/endpoints/benchmarks/benchmarks.module.ts
+++ b/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';
diff --git a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts b/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts
index 1b8788ecb7..e77dea9eeb 100644
--- a/apps/api/src/app/endpoints/data-providers/ghostfolio/ghostfolio.module.ts
+++ b/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';
diff --git a/apps/api/src/app/endpoints/public/public.module.ts b/apps/api/src/app/endpoints/public/public.module.ts
index d94f350c36..4e4c4b7c00 100644
--- a/apps/api/src/app/endpoints/public/public.module.ts
+++ b/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';
diff --git a/apps/api/src/app/health/health.module.ts b/apps/api/src/app/health/health.module.ts
index c36924121c..7b517ef013 100644
--- a/apps/api/src/app/health/health.module.ts
+++ b/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';
diff --git a/apps/api/src/app/health/health.service.ts b/apps/api/src/app/health/health.service.ts
index 42a0be61b8..6cfd5cbce3 100644
--- a/apps/api/src/app/health/health.service.ts
+++ b/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';
diff --git a/apps/api/src/app/import/import.module.ts b/apps/api/src/app/import/import.module.ts
index 1a04e7505b..2824d129dd 100644
--- a/apps/api/src/app/import/import.module.ts
+++ b/apps/api/src/app/import/import.module.ts
@@ -3,7 +3,6 @@ import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.modu
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';
@@ -13,6 +12,7 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d
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';
diff --git a/apps/api/src/app/info/info.module.ts b/apps/api/src/app/info/info.module.ts
index 06b7249095..a66b75bdd6 100644
--- a/apps/api/src/app/info/info.module.ts
+++ b/apps/api/src/app/info/info.module.ts
@@ -1,5 +1,4 @@
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';
@@ -10,6 +9,7 @@ import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-d
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.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';
diff --git a/apps/api/src/app/info/info.service.ts b/apps/api/src/app/info/info.service.ts
index 2a7382446c..ab373055bf 100644
--- a/apps/api/src/app/info/info.service.ts
+++ b/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,
diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
index 48ab8f3218..138ee1020f 100644
--- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.factory.ts
+++ b/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,
diff --git a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts b/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
index 4ef6a95841..7d0c2ddda9 100644
--- a/apps/api/src/app/portfolio/calculator/portfolio-calculator.ts
+++ b/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
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts
index 5d3cbcf320..15f61d0614 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-buy.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
index 54f1dfcb48..9d114d8dce 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell-in-two-activities.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
index 9ee4877180..6bd391683b 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy-and-sell.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
index afae22c1c6..de507791c7 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-baln-buy.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts
index 5c78f8a42b..f3f069eeae 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur-in-base-currency-eur.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
index ed8841b167..219b988f5b 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btceur.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
index 507dd731e2..1ff6f55d59 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-before-first-market-price.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-before-first-market-price.spec.ts
index 6b3d9ce6d4..bd8d76948c 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-buy-before-first-market-price.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts
index 356fdebd03..30201c34f7 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd-short.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
index 0d0f6cfc3b..05ac3f5072 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-btcusd.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
index b98aa9e5f2..67aa90899e 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-cash.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts
index 6e9a42e3e8..06c68f8773 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-fee.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts
index 46dbe493bf..1a5af99daf 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy-with-dividend.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
index e048d2df51..f375a32419 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-buy.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts
index bb6385d18d..891d4397e0 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-googl-fee-and-dividend.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts
index b2e0ffab6b..2c5aec649b 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-jnug-buy-and-sell-and-buy-and-sell.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts
index 370205fc98..8e9b87bf31 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-liability.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-before-first-market-price.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-before-first-market-price.spec.ts
index 135e6f6407..47ac8df4c7 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-before-first-market-price.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-with-market-price-between-chart-dates.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-with-market-price-between-chart-dates.spec.ts
index 989409e77b..9658d998d0 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-manual-buy-with-market-price-between-chart-dates.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts
index 1db23bc6cb..2ca108430d 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-and-sell.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts
index 00b3ce4af1..d420ee873b 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-from-two-data-sources.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
index 2b89ddd31b..b3e67a0793 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-msft-buy-with-dividend.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-activities.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-activities.spec.ts
index f248114fe4..2326e3486f 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-no-activities.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
index e85a053e61..1e12a6aa2c 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell-partially.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
index a0174b048e..f5c7923853 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-novn-buy-and-sell.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-stock-split.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-stock-split.spec.ts
index f45e453734..1292cbe04f 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-stock-split.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts
index 3d0ab45f85..69df7ac49b 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator-valuable.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts b/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts
index c2779c2c14..9400ec20ff 100644
--- a/apps/api/src/app/portfolio/calculator/roai/portfolio-calculator.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-buy.spec.ts
index a7167a2de1..e2c7d1e5a8 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-buy.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-sell.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-sell.spec.ts
index 02300923dd..3a228349c2 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy-and-sell.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy.spec.ts
index c590f4503f..9bfcd12b2a 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-buy.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-sell-and-buy-on-same-day.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-sell-and-buy-on-same-day.spec.ts
index 6a66322fcb..080901f3a5 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-baln-sell-and-buy-on-same-day.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
index 3d6a9e35ad..8aa8c6bb07 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-btcusd-buy-and-sell-partially.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-cash.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-cash.spec.ts
index 59060483ce..1f44b6ce2b 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-cash.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-msft-buy-with-dividend.spec.ts b/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-msft-buy-with-dividend.spec.ts
index 0c0e32d931..31173e4f14 100644
--- a/apps/api/src/app/portfolio/calculator/roi/portfolio-calculator-msft-buy-with-dividend.spec.ts
+++ b/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;
diff --git a/apps/api/src/app/portfolio/portfolio.module.ts b/apps/api/src/app/portfolio/portfolio.module.ts
index 7f7a894df0..82eeb5395a 100644
--- a/apps/api/src/app/portfolio/portfolio.module.ts
+++ b/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';
diff --git a/apps/api/src/events/events.module.ts b/apps/api/src/events/events.module.ts
index dabc3edb77..b04a59a00e 100644
--- a/apps/api/src/events/events.module.ts
+++ b/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';
diff --git a/apps/api/src/events/portfolio-changed.listener.ts b/apps/api/src/events/portfolio-changed.listener.ts
index 9397eb0e52..a081e1bbdb 100644
--- a/apps/api/src/events/portfolio-changed.listener.ts
+++ b/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,
diff --git a/apps/api/src/services/benchmark/benchmark.module.ts b/apps/api/src/services/benchmark/benchmark.module.ts
index 870ef244fe..561a951971 100644
--- a/apps/api/src/services/benchmark/benchmark.module.ts
+++ b/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';
diff --git a/apps/api/src/services/benchmark/benchmark.service.ts b/apps/api/src/services/benchmark/benchmark.service.ts
index 3c39848401..ea7bbaec12 100644
--- a/apps/api/src/services/benchmark/benchmark.service.ts
+++ b/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,
diff --git a/apps/api/src/services/data-provider/data-provider.module.ts b/apps/api/src/services/data-provider/data-provider.module.ts
index 2c6e9fce18..e932d387fd 100644
--- a/apps/api/src/services/data-provider/data-provider.module.ts
+++ b/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';
diff --git a/apps/api/src/services/data-provider/data-provider.service.spec.ts b/apps/api/src/services/data-provider/data-provider.service.spec.ts
index 20f5acaadb..815ab2743e 100644
--- a/apps/api/src/services/data-provider/data-provider.service.spec.ts
+++ b/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';
diff --git a/apps/api/src/services/data-provider/data-provider.service.ts b/apps/api/src/services/data-provider/data-provider.service.ts
index 3206b2442b..826e14d16a 100644
--- a/apps/api/src/services/data-provider/data-provider.service.ts
+++ b/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,
diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts
index 0da5298211..617bf1aae4 100644
--- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.module.ts
+++ b/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
diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts
index adc66e6028..b9628f9447 100644
--- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.processor.ts
+++ b/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,
diff --git a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts b/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts
index fddbd01aba..11b794dfc8 100644
--- a/apps/api/src/services/queues/portfolio-snapshot/portfolio-snapshot.service.mock.ts
+++ b/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';
diff --git a/apps/api/src/app/redis-cache/redis-cache.module.ts b/apps/api/src/services/redis-cache/redis-cache.module.ts
similarity index 100%
rename from apps/api/src/app/redis-cache/redis-cache.module.ts
rename to apps/api/src/services/redis-cache/redis-cache.module.ts
diff --git a/apps/api/src/app/redis-cache/redis-cache.service.mock.ts b/apps/api/src/services/redis-cache/redis-cache.service.mock.ts
similarity index 100%
rename from apps/api/src/app/redis-cache/redis-cache.service.mock.ts
rename to apps/api/src/services/redis-cache/redis-cache.service.mock.ts
diff --git a/apps/api/src/app/redis-cache/redis-cache.service.ts b/apps/api/src/services/redis-cache/redis-cache.service.ts
similarity index 100%
rename from apps/api/src/app/redis-cache/redis-cache.service.ts
rename to apps/api/src/services/redis-cache/redis-cache.service.ts
From cea7c303c6d64c5b2fd6d14f3cb1751f24901b1c Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 19:41:47 +0200
Subject: [PATCH 12/18] Task/deprecate platform endpoints in favor of platforms
endpoints (#8255)
* Deprecate platform endpoints in favor of platforms endpoints
* Update changelog
---
CHANGELOG.md | 1 +
.../app/__snapshots__/app.module.spec.ts.snap | 28 +++++
apps/api/src/app/app.module.ts | 2 -
.../platforms}/platform.controller.ts | 4 +-
.../platforms/platforms.controller.ts | 113 +++++++++++++++++-
.../endpoints/platforms/platforms.module.ts | 5 +-
apps/api/src/app/import/import.module.ts | 2 +-
apps/api/src/app/import/import.service.ts | 2 +-
apps/api/src/app/info/info.module.ts | 2 +-
.../platform/platform.module.ts | 2 -
.../platform/platform.service.ts | 7 +-
.../admin-platform.component.ts | 20 ++--
libs/common/src/lib/interfaces/index.ts | 2 +
.../responses/platform-response.interface.ts | 6 +
.../responses/platforms-response.interface.ts | 4 +-
libs/ui/src/lib/services/admin.service.ts | 29 +----
libs/ui/src/lib/services/data.service.ts | 18 +++
17 files changed, 191 insertions(+), 56 deletions(-)
rename apps/api/src/app/{platform => endpoints/platforms}/platform.controller.ts (95%)
rename apps/api/src/{app => services}/platform/platform.module.ts (76%)
rename apps/api/src/{app => services}/platform/platform.service.ts (92%)
create mode 100644 libs/common/src/lib/interfaces/responses/platform-response.interface.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 11cae3d5e6..20cd0771a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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
diff --git a/apps/api/src/app/__snapshots__/app.module.spec.ts.snap b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
index 34cdbb907a..68defd9062 100644
--- a/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
+++ b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
@@ -134,6 +134,15 @@ exports[`AppModule should keep the security of each route 1`] = `
"interceptors": [],
"permission": "deletePlatform",
},
+ "DELETE /platforms/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deletePlatform",
+ },
"DELETE /tags/:id": {
"allowDuringImpersonation": true,
"guards": [
@@ -557,6 +566,7 @@ exports[`AppModule should keep the security of each route 1`] = `
"permission": "readPlatformsWithAccountCount",
},
"GET /platforms": {
+ "allowDuringImpersonation": true,
"guards": [
"AuthGuard('jwt')",
"HasPermissionGuard",
@@ -1021,6 +1031,15 @@ exports[`AppModule should keep the security of each route 1`] = `
"interceptors": [],
"permission": "createPlatform",
},
+ "POST /platforms": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "createPlatform",
+ },
"POST /subscription/redeem-coupon": {
"allowDuringImpersonation": true,
"guards": [
@@ -1144,6 +1163,15 @@ exports[`AppModule should keep the security of each route 1`] = `
"interceptors": [],
"permission": "updatePlatform",
},
+ "PUT /platforms/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "updatePlatform",
+ },
"PUT /portfolio/holding/:dataSource/:symbol/tags": {
"guards": [
"AuthGuard('jwt')",
diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts
index 27fdff6b64..4286bf7870 100644
--- a/apps/api/src/app/app.module.ts
+++ b/apps/api/src/app/app.module.ts
@@ -63,7 +63,6 @@ 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 { SubscriptionModule } from './subscription/subscription.module';
import { SymbolModule } from './symbol/symbol.module';
@@ -130,7 +129,6 @@ import { UserModule } from './user/user.module';
LogoModule,
MarketDataModule,
McpModule,
- PlatformModule,
PlatformsModule,
PortfolioModule,
PortfolioSnapshotQueueModule,
diff --git a/apps/api/src/app/platform/platform.controller.ts b/apps/api/src/app/endpoints/platforms/platform.controller.ts
similarity index 95%
rename from apps/api/src/app/platform/platform.controller.ts
rename to apps/api/src/app/endpoints/platforms/platform.controller.ts
index 6c0af45158..bddc1b9aa7 100644
--- a/apps/api/src/app/platform/platform.controller.ts
+++ b/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 {
diff --git a/apps/api/src/app/endpoints/platforms/platforms.controller.ts b/apps/api/src/app/endpoints/platforms/platforms.controller.ts
index 92ba772971..11d3972a81 100644
--- a/apps/api/src/app/endpoints/platforms/platforms.controller.ts
+++ b/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 {
+ 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 {
+ 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 {
+ 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 {
+ 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']);
}
}
diff --git a/apps/api/src/app/endpoints/platforms/platforms.module.ts b/apps/api/src/app/endpoints/platforms/platforms.module.ts
index 21d0edf69d..5af50fd63d 100644
--- a/apps/api/src/app/endpoints/platforms/platforms.module.ts
+++ b/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 {}
diff --git a/apps/api/src/app/import/import.module.ts b/apps/api/src/app/import/import.module.ts
index 2824d129dd..f972448863 100644
--- a/apps/api/src/app/import/import.module.ts
+++ b/apps/api/src/app/import/import.module.ts
@@ -1,7 +1,6 @@
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 { 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';
@@ -10,6 +9,7 @@ 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';
diff --git a/apps/api/src/app/import/import.service.ts b/apps/api/src/app/import/import.service.ts
index 6f036479c7..2d09cb78df 100644
--- a/apps/api/src/app/import/import.service.ts
+++ b/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';
diff --git a/apps/api/src/app/info/info.module.ts b/apps/api/src/app/info/info.module.ts
index a66b75bdd6..7d30a7b53f 100644
--- a/apps/api/src/app/info/info.module.ts
+++ b/apps/api/src/app/info/info.module.ts
@@ -1,4 +1,3 @@
-import { PlatformModule } from '@ghostfolio/api/app/platform/platform.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';
@@ -7,6 +6,7 @@ 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';
diff --git a/apps/api/src/app/platform/platform.module.ts b/apps/api/src/services/platform/platform.module.ts
similarity index 76%
rename from apps/api/src/app/platform/platform.module.ts
rename to apps/api/src/services/platform/platform.module.ts
index 42ae9d193d..849e037eda 100644
--- a/apps/api/src/app/platform/platform.module.ts
+++ b/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]
diff --git a/apps/api/src/app/platform/platform.service.ts b/apps/api/src/services/platform/platform.service.ts
similarity index 92%
rename from apps/api/src/app/platform/platform.service.ts
rename to apps/api/src/services/platform/platform.service.ts
index 200b4de007..56da19b2a5 100644
--- a/apps/api/src/app/platform/platform.service.ts
+++ b/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 }
diff --git a/apps/client/src/app/components/admin-platform/admin-platform.component.ts b/apps/client/src/app/components/admin-platform/admin-platform.component.ts
index 89175c5d33..10634d5dfb 100644
--- a/apps/client/src/app/components/admin-platform/admin-platform.component.ts
+++ b/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();
+ 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({
diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts
index 5f22f4734e..c39fd74c55 100644
--- a/libs/common/src/lib/interfaces/index.ts
+++ b/libs/common/src/lib/interfaces/index.ts
@@ -75,6 +75,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';
@@ -170,6 +171,7 @@ export {
MarketDataOfMarketsResponse,
NullableLineChartItem,
OAuthResponse,
+ PlatformResponse,
PlatformsResponse,
PortfolioChart,
PortfolioDetails,
diff --git a/libs/common/src/lib/interfaces/responses/platform-response.interface.ts b/libs/common/src/lib/interfaces/responses/platform-response.interface.ts
new file mode 100644
index 0000000000..7c8bee930f
--- /dev/null
+++ b/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'
+> {}
diff --git a/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts b/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
index a6bfc4326c..4f9e98054f 100644
--- a/libs/common/src/lib/interfaces/responses/platforms-response.interface.ts
+++ b/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 })[];
}
diff --git a/libs/ui/src/lib/services/admin.service.ts b/libs/ui/src/lib/services/admin.service.ts
index 0307909dc1..78314c3b9b 100644
--- a/libs/ui/src/lib/services/admin.service.ts
+++ b/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(`/api/v1/platform/${aId}`);
- }
-
public deleteAssetProfileSplit({
dataSource,
id,
@@ -113,10 +103,6 @@ export class AdminService {
});
}
- public fetchPlatforms() {
- return this.http.get('/api/v1/platform');
- }
-
public fetchUserById(id: string) {
return this.http.get(`/api/v1/admin/user/${id}`);
}
@@ -252,17 +238,6 @@ export class AdminService {
);
}
- public postPlatform(aPlatform: CreatePlatformDto) {
- return this.http.post(`/api/v1/platform`, aPlatform);
- }
-
- public putPlatform(aPlatform: UpdatePlatformDto) {
- return this.http.put(
- `/api/v1/platform/${aPlatform.id}`,
- aPlatform
- );
- }
-
public syncDemoUserAccount() {
return this.http.get(`/api/v1/admin/demo-user/sync`);
}
diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts
index a74dabded0..ca10f91b52 100644
--- a/libs/ui/src/lib/services/data.service.ts
+++ b/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(`/api/v1/user`, { body: aData });
}
+ public deletePlatform(aId: string) {
+ return this.http.delete(`/api/v1/platforms/${aId}`);
+ }
+
public deleteTag(aId: string) {
return this.http.delete(`/api/v1/tags/${aId}`);
}
@@ -914,6 +921,10 @@ export class DataService {
return this.http.post(url, marketData);
}
+ public postPlatform(aPlatform: CreatePlatformDto) {
+ return this.http.post(`/api/v1/platforms`, aPlatform);
+ }
+
public postTag(aTag: CreateTagDto) {
return this.http.post(`/api/v1/tags`, aTag);
}
@@ -953,6 +964,13 @@ export class DataService {
);
}
+ public putPlatform(aPlatform: UpdatePlatformDto) {
+ return this.http.put(
+ `/api/v1/platforms/${aPlatform.id}`,
+ aPlatform
+ );
+ }
+
public putTag(aTag: UpdateTagDto) {
return this.http.put(`/api/v1/tags/${aTag.id}`, aTag);
}
From e96166ad0386f9502a198ca6113dff3a568bc036 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Tue, 20 Oct 2026 20:03:35 +0200
Subject: [PATCH 13/18] Task/improve layout of user account settings on mobile
(#8229)
* Improve layout on mobile
* Update changelog
---
CHANGELOG.md | 1 +
.../user-account-settings.html | 67 ++++++++++---------
2 files changed, 35 insertions(+), 33 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20cd0771a7..ef981159b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ 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
diff --git a/apps/client/src/app/components/user-account-settings/user-account-settings.html b/apps/client/src/app/components/user-account-settings/user-account-settings.html
index ff86f9ba35..577497848a 100644
--- a/apps/client/src/app/components/user-account-settings/user-account-settings.html
+++ b/apps/client/src/app/components/user-account-settings/user-account-settings.html
@@ -5,14 +5,15 @@
-
+
Base Currency
-
+
@if (user?.settings?.isExperimentalFeatures) {
-
-
+
+
Performance Calculation
-
+
}
-
-
+
+
Language
@if (isCommunityLanguage()) {
@@ -77,7 +78,7 @@
}
-
-
-
+
+
Locale
-
-
-
-
+
+
Appearance
-
-
-
+
+
Presenter View
Protection for sensitive information like absolute performances and
quantity values
-
-
-
+
+
Zen Mode
Distraction-free experience for turbulent times
-
-
-
+
+
Biometric Authentication
Sign in with fingerprint
-
-
-
+
+
Experimental Features
Sneak peek at upcoming functionality
-
+
@if (deviceType() !== 'mobile' && hasPermissionToEnableGhostfolioAi) {
-
-
+
+
Ghostfolio AI
Powered by third-party AI providers
-
+
Date: Tue, 20 Oct 2026 20:06:36 +0200
Subject: [PATCH 14/18] Release 3.89.0 (#8257)
---
CHANGELOG.md | 2 +-
package-lock.json | 4 ++--
package.json | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef981159b6..f011708ef3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## Unreleased
+## 3.89.0 - 2026-10-20
### Added
diff --git a/package-lock.json b/package-lock.json
index 61d6a0c375..4aa91be806 100644
--- a/package-lock.json
+++ b/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": {
diff --git a/package.json b/package.json
index 1999175cf9..305d8fb12a 100644
--- a/package.json
+++ b/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",
From 3f36146279649a78076fc7121afa7e8007ce4106 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 21 Oct 2026 08:10:35 +0200
Subject: [PATCH 15/18] Task/upgrade @modelcontextprotocol dependencies
(20261021) (#8261)
* Update @modelcontextprotocol dependencies
* Update changelog
---
CHANGELOG.md | 7 +++++++
package-lock.json | 43 +++++++++++++++++++------------------------
package.json | 6 +++---
3 files changed, 29 insertions(+), 27 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f011708ef3..10c35c9f3c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### 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
diff --git a/package-lock.json b/package-lock.json
index 4aa91be806..f1d3ec50e1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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": {
diff --git a/package.json b/package.json
index 305d8fb12a..3585947d2a 100644
--- a/package.json
+++ b/package.json
@@ -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",
From ab763c59ab6aec310d52640559f64a7b9c3e09aa Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 21 Oct 2026 12:02:09 +0200
Subject: [PATCH 16/18] Task/upgrade mcp-nest to version 2.0.8 (#8262)
* Update @rekog/mcp-nest to version 2.0.8
* Update changelog
---
CHANGELOG.md | 1 +
package-lock.json | 8 ++++----
package.json | 2 +-
3 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 10c35c9f3c..9ee2e4022c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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`
+- Upgraded `@rekog/mcp-nest` from version `2.0.7` to `2.0.8`
## 3.89.0 - 2026-10-20
diff --git a/package-lock.json b/package-lock.json
index f1d3ec50e1..faf228c31a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -49,7 +49,7 @@
"@openrouter/ai-sdk-provider": "3.1.0",
"@prisma/adapter-pg": "7.10.0",
"@prisma/client": "7.10.0",
- "@rekog/mcp-nest": "2.0.7",
+ "@rekog/mcp-nest": "2.0.8",
"@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "14.0.3",
"ai": "7.0.127",
@@ -12753,9 +12753,9 @@
}
},
"node_modules/@rekog/mcp-nest": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/@rekog/mcp-nest/-/mcp-nest-2.0.7.tgz",
- "integrity": "sha512-8DWrSapEA9Z/YJL9dFmj7sSbemmeQmf0VkGkW6Sn1itwKFf0n9hzTiwWt8XBtZvV5JVDNCtvRZKYYKgNjDcYPg==",
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@rekog/mcp-nest/-/mcp-nest-2.0.8.tgz",
+ "integrity": "sha512-CDqzT1RBdvCNsRkDthcNPG3nxoy0GbRCrdrK+TjL+90ElL+j65lquBhUQcR37csGyeLa8uLnj/2unLqlKZRzoA==",
"license": "MIT",
"dependencies": {
"multer": "^2.3.0",
diff --git a/package.json b/package.json
index 3585947d2a..4348407e87 100644
--- a/package.json
+++ b/package.json
@@ -93,7 +93,7 @@
"@openrouter/ai-sdk-provider": "3.1.0",
"@prisma/adapter-pg": "7.10.0",
"@prisma/client": "7.10.0",
- "@rekog/mcp-nest": "2.0.7",
+ "@rekog/mcp-nest": "2.0.8",
"@simplewebauthn/browser": "14.0.0",
"@simplewebauthn/server": "14.0.3",
"ai": "7.0.127",
From edc9806c282d8b2bc9e0e892d434ea772ffa572a Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 21 Oct 2026 12:02:58 +0200
Subject: [PATCH 17/18] Task/remove unused fetchExchangeRateForDate() method
(#8265)
Clean up
---
libs/ui/src/lib/services/data.service.ts | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/libs/ui/src/lib/services/data.service.ts b/libs/ui/src/lib/services/data.service.ts
index ca10f91b52..6292f8770a 100644
--- a/libs/ui/src/lib/services/data.service.ts
+++ b/libs/ui/src/lib/services/data.service.ts
@@ -40,7 +40,6 @@ import {
CreateActivityResponse,
CreateStripeCheckoutSessionResponse,
DataProviderHealthResponse,
- DataProviderHistoricalResponse,
ExportResponse,
Filter,
ImportResponse,
@@ -309,18 +308,6 @@ export class DataService {
);
}
- public fetchExchangeRateForDate({
- date,
- symbol
- }: {
- date: Date;
- symbol: string;
- }) {
- return this.http.get(
- `/api/v1/exchange-rate/${encodeURIComponent(symbol)}/${format(date, DATE_FORMAT, { in: utc })}`
- );
- }
-
public deleteAccess(aId: string) {
return this.http.delete(`/api/v1/access/${aId}`);
}
From f144c723f471ef04d4b4c1e2571db428b53dfbc3 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Wed, 21 Oct 2026 13:44:00 +0200
Subject: [PATCH 18/18] Task/deprecate auth device and exchange rate endpoints
(#8256)
* Deprecate auth device and exchange rate endpoints
* Update changelog
---
CHANGELOG.md | 2 ++
.../app/__snapshots__/app.module.spec.ts.snap | 16 ++++++++++++++++
apps/api/src/app/app.module.ts | 8 ++++----
.../src/app/auth-device/auth-device.module.ts | 19 -------------------
apps/api/src/app/auth/auth.module.ts | 4 ++--
apps/api/src/app/auth/web-auth.service.ts | 2 +-
.../auth-devices/auth-devices.controller.ts} | 10 +++++++---
.../auth-devices/auth-devices.module.ts | 11 +++++++++++
.../exchange-rates.controller.ts} | 18 +++++++++++-------
.../exchange-rates/exchange-rates.module.ts | 13 +++++++++++++
.../exchange-rates/exchange-rates.service.ts} | 2 +-
.../app/exchange-rate/exchange-rate.module.ts | 14 --------------
.../auth-device/auth-device.module.ts | 12 ++++++++++++
.../auth-device/auth-device.service.ts | 0
.../src/app/services/web-authn.service.ts | 2 +-
libs/common/src/lib/interfaces/index.ts | 2 ++
.../exchange-rate-response.interface.ts | 3 +++
17 files changed, 86 insertions(+), 52 deletions(-)
delete mode 100644 apps/api/src/app/auth-device/auth-device.module.ts
rename apps/api/src/app/{auth-device/auth-device.controller.ts => endpoints/auth-devices/auth-devices.controller.ts} (87%)
create mode 100644 apps/api/src/app/endpoints/auth-devices/auth-devices.module.ts
rename apps/api/src/app/{exchange-rate/exchange-rate.controller.ts => endpoints/exchange-rates/exchange-rates.controller.ts} (65%)
create mode 100644 apps/api/src/app/endpoints/exchange-rates/exchange-rates.module.ts
rename apps/api/src/app/{exchange-rate/exchange-rate.service.ts => endpoints/exchange-rates/exchange-rates.service.ts} (94%)
delete mode 100644 apps/api/src/app/exchange-rate/exchange-rate.module.ts
create mode 100644 apps/api/src/services/auth-device/auth-device.module.ts
rename apps/api/src/{app => services}/auth-device/auth-device.service.ts (100%)
create mode 100644 libs/common/src/lib/interfaces/responses/exchange-rate-response.interface.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ee2e4022c..ed0964923f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
+- Deprecated the `api/v1/auth-device` endpoint in favor of the `api/v1/auth-devices` endpoint
+- Deprecated the `api/v1/exchange-rate` endpoint in favor of the `api/v1/exchange-rates` endpoint
- 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`
- Upgraded `@rekog/mcp-nest` from version `2.0.7` to `2.0.8`
diff --git a/apps/api/src/app/__snapshots__/app.module.spec.ts.snap b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
index 68defd9062..f5e8b793d4 100644
--- a/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
+++ b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap
@@ -116,6 +116,15 @@ exports[`AppModule should keep the security of each route 1`] = `
"interceptors": [],
"permission": "deleteAuthDevice",
},
+ "DELETE /auth-devices/:id": {
+ "allowDuringImpersonation": true,
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ "permission": "deleteAuthDevice",
+ },
"DELETE /benchmarks/:dataSource/:symbol": {
"allowDuringImpersonation": true,
"guards": [
@@ -489,6 +498,13 @@ exports[`AppModule should keep the security of each route 1`] = `
],
"interceptors": [],
},
+ "GET /exchange-rates/:symbol/:dateString": {
+ "guards": [
+ "AuthGuard('jwt')",
+ "HasPermissionGuard",
+ ],
+ "interceptors": [],
+ },
"GET /export": {
"guards": [
"AuthGuard('jwt')",
diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts
index 4286bf7870..7cfba8455c 100644
--- a/apps/api/src/app/app.module.ts
+++ b/apps/api/src/app/app.module.ts
@@ -41,15 +41,16 @@ import { ActivitiesModule } from './activities/activities.module';
import { AdminModule } from './admin/admin.module';
import { AppController } from './app.controller';
import { AssetModule } from './asset/asset.module';
-import { AuthDeviceModule } from './auth-device/auth-device.module';
import { AuthModule } from './auth/auth.module';
import { CacheModule } from './cache/cache.module';
import { AiModule } from './endpoints/ai/ai.module';
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
import { AssetProfilesModule } from './endpoints/asset-profiles/asset-profiles.module';
import { AssetsModule } from './endpoints/assets/assets.module';
+import { AuthDevicesModule } from './endpoints/auth-devices/auth-devices.module';
import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module';
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
+import { ExchangeRatesModule } from './endpoints/exchange-rates/exchange-rates.module';
import { MarketDataModule } from './endpoints/market-data/market-data.module';
import { McpModule } from './endpoints/mcp/mcp.module';
import { PlatformsModule } from './endpoints/platforms/platforms.module';
@@ -57,7 +58,6 @@ import { PublicModule } from './endpoints/public/public.module';
import { SitemapModule } from './endpoints/sitemap/sitemap.module';
import { TagsModule } from './endpoints/tags/tags.module';
import { WatchlistModule } from './endpoints/watchlist/watchlist.module';
-import { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
import { ExportModule } from './export/export.module';
import { HealthModule } from './health/health.module';
import { ImportModule } from './import/import.module';
@@ -80,7 +80,7 @@ import { UserModule } from './user/user.module';
AssetProfilesModule,
AssetModule,
AssetsModule,
- AuthDeviceModule,
+ AuthDevicesModule,
AuthModule,
BenchmarksModule,
BullBoardModule.forRoot({
@@ -119,8 +119,8 @@ import { UserModule } from './user/user.module';
DataProviderModule,
EventEmitterModule.forRoot(),
EventsModule,
- ExchangeRateModule,
ExchangeRateDataModule,
+ ExchangeRatesModule,
ExportModule,
GhostfolioModule,
HealthModule,
diff --git a/apps/api/src/app/auth-device/auth-device.module.ts b/apps/api/src/app/auth-device/auth-device.module.ts
deleted file mode 100644
index 515efa155c..0000000000
--- a/apps/api/src/app/auth-device/auth-device.module.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { AuthDeviceController } from '@ghostfolio/api/app/auth-device/auth-device.controller';
-import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
-import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
-
-import { Module } from '@nestjs/common';
-import { JwtModule } from '@nestjs/jwt';
-
-@Module({
- controllers: [AuthDeviceController],
- imports: [
- JwtModule.register({
- secret: process.env.JWT_SECRET_KEY,
- signOptions: { expiresIn: '180 days' }
- }),
- PrismaModule
- ],
- providers: [AuthDeviceService]
-})
-export class AuthDeviceModule {}
diff --git a/apps/api/src/app/auth/auth.module.ts b/apps/api/src/app/auth/auth.module.ts
index 1eafdb6091..afeebe98d0 100644
--- a/apps/api/src/app/auth/auth.module.ts
+++ b/apps/api/src/app/auth/auth.module.ts
@@ -1,9 +1,9 @@
-import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
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';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
+import { AuthDeviceModule } from '@ghostfolio/api/services/auth-device/auth-device.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { FetchModule } from '@ghostfolio/api/services/fetch/fetch.module';
@@ -29,6 +29,7 @@ import { OidcStrategy } from './oidc.strategy';
controllers: [AuthController],
imports: [
ApiModule,
+ AuthDeviceModule,
ConfigurationModule,
FetchModule,
JwtModule.register({
@@ -45,7 +46,6 @@ import { OidcStrategy } from './oidc.strategy';
providers: [
ApiKeyService,
ApiKeyStrategy,
- AuthDeviceService,
AuthService,
GoogleStrategy,
JwtStrategy,
diff --git a/apps/api/src/app/auth/web-auth.service.ts b/apps/api/src/app/auth/web-auth.service.ts
index fe9a9467b2..3bae3de88f 100644
--- a/apps/api/src/app/auth/web-auth.service.ts
+++ b/apps/api/src/app/auth/web-auth.service.ts
@@ -1,7 +1,7 @@
-import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { PortfolioSnapshotValue } from '@ghostfolio/api/app/portfolio/interfaces/snapshot-value.interface';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
+import { AuthDeviceService } from '@ghostfolio/api/services/auth-device/auth-device.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';
diff --git a/apps/api/src/app/auth-device/auth-device.controller.ts b/apps/api/src/app/endpoints/auth-devices/auth-devices.controller.ts
similarity index 87%
rename from apps/api/src/app/auth-device/auth-device.controller.ts
rename to apps/api/src/app/endpoints/auth-devices/auth-devices.controller.ts
index 509746ca97..7577c16901 100644
--- a/apps/api/src/app/auth-device/auth-device.controller.ts
+++ b/apps/api/src/app/endpoints/auth-devices/auth-devices.controller.ts
@@ -1,7 +1,7 @@
-import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.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 { AuthDeviceService } from '@ghostfolio/api/services/auth-device/auth-device.service';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
@@ -18,8 +18,12 @@ import { AuthGuard } from '@nestjs/passport';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
@AllowDuringImpersonation()
-@Controller('auth-device')
-export class AuthDeviceController {
+@Controller([
+ 'auth-devices',
+ /** @deprecated */
+ 'auth-device'
+])
+export class AuthDevicesController {
public constructor(
private readonly authDeviceService: AuthDeviceService,
@Inject(REQUEST) private readonly request: RequestWithUser
diff --git a/apps/api/src/app/endpoints/auth-devices/auth-devices.module.ts b/apps/api/src/app/endpoints/auth-devices/auth-devices.module.ts
new file mode 100644
index 0000000000..ce5042d264
--- /dev/null
+++ b/apps/api/src/app/endpoints/auth-devices/auth-devices.module.ts
@@ -0,0 +1,11 @@
+import { AuthDeviceModule } from '@ghostfolio/api/services/auth-device/auth-device.module';
+
+import { Module } from '@nestjs/common';
+
+import { AuthDevicesController } from './auth-devices.controller';
+
+@Module({
+ controllers: [AuthDevicesController],
+ imports: [AuthDeviceModule]
+})
+export class AuthDevicesModule {}
diff --git a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts b/apps/api/src/app/endpoints/exchange-rates/exchange-rates.controller.ts
similarity index 65%
rename from apps/api/src/app/exchange-rate/exchange-rate.controller.ts
rename to apps/api/src/app/endpoints/exchange-rates/exchange-rates.controller.ts
index 239b4b27a1..7a873b11db 100644
--- a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts
+++ b/apps/api/src/app/endpoints/exchange-rates/exchange-rates.controller.ts
@@ -1,5 +1,5 @@
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
-import { DataProviderHistoricalResponse } from '@ghostfolio/common/interfaces';
+import { ExchangeRateResponse } from '@ghostfolio/common/interfaces';
import {
Controller,
@@ -12,12 +12,16 @@ import { AuthGuard } from '@nestjs/passport';
import { parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
-import { ExchangeRateService } from './exchange-rate.service';
+import { ExchangeRatesService } from './exchange-rates.service';
-@Controller('exchange-rate')
-export class ExchangeRateController {
+@Controller([
+ 'exchange-rates',
+ /** @deprecated */
+ 'exchange-rate'
+])
+export class ExchangeRatesController {
public constructor(
- private readonly exchangeRateService: ExchangeRateService
+ private readonly exchangeRatesService: ExchangeRatesService
) {}
@Get(':symbol/:dateString')
@@ -25,10 +29,10 @@ export class ExchangeRateController {
public async getExchangeRate(
@Param('dateString') dateString: string,
@Param('symbol') symbol: string
- ): Promise {
+ ): Promise {
const date = parseISO(dateString);
- const exchangeRate = await this.exchangeRateService.getExchangeRate({
+ const exchangeRate = await this.exchangeRatesService.getExchangeRate({
date,
symbol
});
diff --git a/apps/api/src/app/endpoints/exchange-rates/exchange-rates.module.ts b/apps/api/src/app/endpoints/exchange-rates/exchange-rates.module.ts
new file mode 100644
index 0000000000..c143ddc336
--- /dev/null
+++ b/apps/api/src/app/endpoints/exchange-rates/exchange-rates.module.ts
@@ -0,0 +1,13 @@
+import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
+
+import { Module } from '@nestjs/common';
+
+import { ExchangeRatesController } from './exchange-rates.controller';
+import { ExchangeRatesService } from './exchange-rates.service';
+
+@Module({
+ controllers: [ExchangeRatesController],
+ imports: [ExchangeRateDataModule],
+ providers: [ExchangeRatesService]
+})
+export class ExchangeRatesModule {}
diff --git a/apps/api/src/app/exchange-rate/exchange-rate.service.ts b/apps/api/src/app/endpoints/exchange-rates/exchange-rates.service.ts
similarity index 94%
rename from apps/api/src/app/exchange-rate/exchange-rate.service.ts
rename to apps/api/src/app/endpoints/exchange-rates/exchange-rates.service.ts
index d70fd534c0..e66cc591ae 100644
--- a/apps/api/src/app/exchange-rate/exchange-rate.service.ts
+++ b/apps/api/src/app/endpoints/exchange-rates/exchange-rates.service.ts
@@ -3,7 +3,7 @@ import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-
import { Injectable } from '@nestjs/common';
@Injectable()
-export class ExchangeRateService {
+export class ExchangeRatesService {
public constructor(
private readonly exchangeRateDataService: ExchangeRateDataService
) {}
diff --git a/apps/api/src/app/exchange-rate/exchange-rate.module.ts b/apps/api/src/app/exchange-rate/exchange-rate.module.ts
deleted file mode 100644
index e1d9d18917..0000000000
--- a/apps/api/src/app/exchange-rate/exchange-rate.module.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
-
-import { Module } from '@nestjs/common';
-
-import { ExchangeRateController } from './exchange-rate.controller';
-import { ExchangeRateService } from './exchange-rate.service';
-
-@Module({
- controllers: [ExchangeRateController],
- exports: [ExchangeRateService],
- imports: [ExchangeRateDataModule],
- providers: [ExchangeRateService]
-})
-export class ExchangeRateModule {}
diff --git a/apps/api/src/services/auth-device/auth-device.module.ts b/apps/api/src/services/auth-device/auth-device.module.ts
new file mode 100644
index 0000000000..0594fccb7d
--- /dev/null
+++ b/apps/api/src/services/auth-device/auth-device.module.ts
@@ -0,0 +1,12 @@
+import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
+
+import { Module } from '@nestjs/common';
+
+import { AuthDeviceService } from './auth-device.service';
+
+@Module({
+ exports: [AuthDeviceService],
+ imports: [PrismaModule],
+ providers: [AuthDeviceService]
+})
+export class AuthDeviceModule {}
diff --git a/apps/api/src/app/auth-device/auth-device.service.ts b/apps/api/src/services/auth-device/auth-device.service.ts
similarity index 100%
rename from apps/api/src/app/auth-device/auth-device.service.ts
rename to apps/api/src/services/auth-device/auth-device.service.ts
diff --git a/apps/client/src/app/services/web-authn.service.ts b/apps/client/src/app/services/web-authn.service.ts
index b769e8506c..37ba6ebf61 100644
--- a/apps/client/src/app/services/web-authn.service.ts
+++ b/apps/client/src/app/services/web-authn.service.ts
@@ -66,7 +66,7 @@ export class WebAuthnService {
const deviceId = this.getDeviceId();
return this.http
- .delete(`/api/v1/auth-device/${deviceId}`)
+ .delete(`/api/v1/auth-devices/${deviceId}`)
.pipe(
catchError((error) => {
console.warn(`Could not deregister device ${deviceId}`, error);
diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts
index c39fd74c55..d17f2377b7 100644
--- a/libs/common/src/lib/interfaces/index.ts
+++ b/libs/common/src/lib/interfaces/index.ts
@@ -67,6 +67,7 @@ import type {
} from './responses/data-provider-response.interface';
import type { DividendsResponse } from './responses/dividends-response.interface';
import type { ResponseError } from './responses/errors.interface';
+import type { ExchangeRateResponse } from './responses/exchange-rate-response.interface';
import type { ExportResponse } from './responses/export-response.interface';
import type { HistoricalResponse } from './responses/historical-response.interface';
import type { ImportResponse } from './responses/import-response.interface';
@@ -150,6 +151,7 @@ export {
DataProviderResponse,
DividendsResponse,
EnhancedAssetProfile,
+ ExchangeRateResponse,
ExportResponse,
Filter,
FilterGroup,
diff --git a/libs/common/src/lib/interfaces/responses/exchange-rate-response.interface.ts b/libs/common/src/lib/interfaces/responses/exchange-rate-response.interface.ts
new file mode 100644
index 0000000000..6991fed809
--- /dev/null
+++ b/libs/common/src/lib/interfaces/responses/exchange-rate-response.interface.ts
@@ -0,0 +1,3 @@
+export interface ExchangeRateResponse {
+ marketPrice: number;
+}