diff --git a/CHANGELOG.md b/CHANGELOG.md index 67fcd56292..a49c4ed191 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,36 @@ 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 `prettier` from version `3.9.9` to `3.9.10` +- Upgraded `@rekog/mcp-nest` from version `2.0.7` to `2.0.8` + +## 3.89.0 - 2026-10-20 + +### Added + +- Added a _Storybook_ story for the file drop area component + +### Changed + +- Improved the layout of the user account settings on mobile +- Improved the usability of the activities page by eliminating the reload on canceling the import activities dialog +- Improved the loading state of the file drop area component - Improved the accessibility of the _Buy me a coffee_ button on the about page +- Improved the accessibility of the entity logo component +- Extracted the file drop area of the import activities dialog to a reusable component - Improved the performance of the historical market data table of the admin control panel by loading only the latest market price per asset profile - Introduced a cooldown of 12 hours for the asset profile and exchange rate gathering on activity creation by retaining the completed jobs -- Upgraded `prettier` from version `3.9.9` to `3.9.10` +- Deprecated the `api/v1/platform` endpoints in favor of the `api/v1/platforms` endpoints + +### Fixed + +- Fixed the missing localization of the date picker in the _FIRE_ calculator +- Fixed the missing localization of the date picker in the asset profile details dialog of the admin control panel +- Fixed the missing tooltip of the client of the last request in the users table of the admin control panel ## 3.88.0 - 2026-10-17 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..f5e8b793d4 --- /dev/null +++ b/apps/api/src/app/__snapshots__/app.module.spec.ts.snap @@ -0,0 +1,1227 @@ +// 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 /auth-devices/: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 /platforms/: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 /exchange-rates/: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": { + "allowDuringImpersonation": true, + "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 /platforms": { + "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 /platforms/: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/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.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(); + }); }); diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 33617602d6..7cfba8455c 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, @@ -40,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'; @@ -56,15 +58,12 @@ 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'; 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'; @@ -81,7 +80,7 @@ import { UserModule } from './user/user.module'; AssetProfilesModule, AssetModule, AssetsModule, - AuthDeviceModule, + AuthDevicesModule, AuthModule, BenchmarksModule, BullBoardModule.forRoot({ @@ -120,8 +119,8 @@ import { UserModule } from './user/user.module'; DataProviderModule, EventEmitterModule.forRoot(), EventsModule, - ExchangeRateModule, ExchangeRateDataModule, + ExchangeRatesModule, ExportModule, GhostfolioModule, HealthModule, @@ -130,7 +129,6 @@ import { UserModule } from './user/user.module'; LogoModule, MarketDataModule, McpModule, - PlatformModule, PlatformsModule, PortfolioModule, PortfolioSnapshotQueueModule, 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 879463cc13..afeebe98d0 100644 --- a/apps/api/src/app/auth/auth.module.ts +++ b/apps/api/src/app/auth/auth.module.ts @@ -1,10 +1,9 @@ -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'; 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'; @@ -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'; @@ -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 40a86398a6..3bae3de88f 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 { 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'; 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/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/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/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/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/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/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/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..f972448863 100644 --- a/apps/api/src/app/import/import.module.ts +++ b/apps/api/src/app/import/import.module.ts @@ -1,9 +1,7 @@ import { AccountModule } from '@ghostfolio/api/app/account/account.module'; import { ActivitiesModule } from '@ghostfolio/api/app/activities/activities.module'; import { CacheModule } from '@ghostfolio/api/app/cache/cache.module'; -import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module'; import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module'; -import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; import { ApiModule } from '@ghostfolio/api/services/api/api.module'; @@ -11,8 +9,10 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration/conf import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; +import { PlatformModule } from '@ghostfolio/api/services/platform/platform.module'; import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; +import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { TagModule } from '@ghostfolio/api/services/tag/tag.module'; 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 06b7249095..7d30a7b53f 100644 --- a/apps/api/src/app/info/info.module.ts +++ b/apps/api/src/app/info/info.module.ts @@ -1,5 +1,3 @@ -import { PlatformModule } from '@ghostfolio/api/app/platform/platform.module'; -import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module'; import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module'; import { UserModule } from '@ghostfolio/api/app/user/user.module'; import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; @@ -8,8 +6,10 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration/conf import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module'; import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module'; import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module'; +import { PlatformModule } from '@ghostfolio/api/services/platform/platform.module'; import { PropertyModule } from '@ghostfolio/api/services/property/property.module'; import { DataGatheringQueueModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module'; +import { RedisCacheModule } from '@ghostfolio/api/services/redis-cache/redis-cache.module'; import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module'; import { Module } from '@nestjs/common'; 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/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/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/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/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 diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index c9e124723d..53d3505a1c 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -25,6 +25,7 @@ import { OnInit } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { DateAdapter } from '@angular/material/core'; import { MatDialog } from '@angular/material/dialog'; import { Title } from '@angular/platform-browser'; import { @@ -39,7 +40,7 @@ import { Chart } from 'chart.js'; import { addIcons } from 'ionicons'; import { openOutline } from 'ionicons/icons'; import { DeviceDetectorService } from 'ngx-device-detector'; -import { filter } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map } from 'rxjs/operators'; import { GfFooterComponent } from './components/footer/footer.component'; import { GfHeaderComponent } from './components/header/header.component'; @@ -75,6 +76,7 @@ export class GfAppComponent implements OnInit { private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly dataService = inject(DataService); + private readonly dateAdapter = inject>(DateAdapter); private readonly destroyRef = inject(DestroyRef); private readonly deviceDetectorService = inject(DeviceDetectorService); private readonly dialog = inject(MatDialog); @@ -212,6 +214,20 @@ export class GfAppComponent implements OnInit { this.changeDetectorRef.markForCheck(); }); + this.userService.stateChanged + .pipe( + map(({ user }) => { + return user?.settings.locale; + }), + distinctUntilChanged(), + takeUntilDestroyed(this.destroyRef) + ) + .subscribe((locale) => { + if (locale) { + this.dateAdapter.setLocale(locale); + } + }); + this.userService.stateChanged .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((state) => { 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/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/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) { 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/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()) { (MAT_DIALOG_DATA); - private readonly dateAdapter = inject>(DateAdapter); private readonly dataService = inject(DataService); private readonly destroyRef = inject(DestroyRef); @@ -189,8 +187,6 @@ export class GfCreateOrUpdateAccessDialogComponent implements OnInit { this.hasExperimentalFeatures = settings.isExperimentalFeatures ?? false; this.tags = getTagFilters(tags); - this.dateAdapter.setLocale(settings.locale ?? DEFAULT_LOCALE); - this.changeDetectorRef.markForCheck(); }); 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
-
+
Date and number format
-
+
{{ 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
-
+
(); - - public onDragEnter(event: DragEvent) { - event.preventDefault(); - event.stopPropagation(); - } - - public onDragOver(event: DragEvent) { - event.preventDefault(); - event.stopPropagation(); - } - - public onDrop(event: DragEvent) { - event.preventDefault(); - event.stopPropagation(); - - if (event.dataTransfer) { - // Prevent the browser's default behavior for handling the file drop - event.dataTransfer.dropEffect = 'copy'; - this.filesDropped.emit(event.dataTransfer.files); - } - } - - public onPaste(event: ClipboardEvent) { - const files = event.clipboardData?.files; - - if (files?.length) { - event.preventDefault(); - this.filesDropped.emit(files); - } - } -} diff --git a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts index dbeedff322..b52339ddfa 100644 --- a/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/activities-page.component.ts @@ -34,7 +34,10 @@ import { format, parseISO } from 'date-fns'; import { DeviceDetectorService } from 'ngx-device-detector'; import { GfImportActivitiesDialogComponent } from './import-activities-dialog/import-activities-dialog.component'; -import { ImportActivitiesDialogParams } from './import-activities-dialog/interfaces/interfaces'; +import { + ImportActivitiesDialogParams, + ImportActivitiesDialogResult +} from './import-activities-dialog/interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -217,7 +220,8 @@ export class GfActivitiesPageComponent implements OnInit { protected onImport() { const dialogRef = this.dialog.open< GfImportActivitiesDialogComponent, - ImportActivitiesDialogParams + ImportActivitiesDialogParams, + ImportActivitiesDialogResult >(GfImportActivitiesDialogComponent, { data: { deviceType: this.deviceType, @@ -230,22 +234,25 @@ export class GfActivitiesPageComponent implements OnInit { dialogRef .afterClosed() .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => { - this.userService - .get(true) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(); + .subscribe(({ withRefresh } = { withRefresh: false }) => { + if (withRefresh) { + this.userService + .get(true) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(); - this.fetchActivities(); + this.fetchActivities(); - this.changeDetectorRef.markForCheck(); + this.changeDetectorRef.markForCheck(); + } }); } protected onImportDividends() { const dialogRef = this.dialog.open< GfImportActivitiesDialogComponent, - ImportActivitiesDialogParams + ImportActivitiesDialogParams, + ImportActivitiesDialogResult >(GfImportActivitiesDialogComponent, { data: { activityTypes: ['DIVIDEND'], @@ -259,15 +266,17 @@ export class GfActivitiesPageComponent implements OnInit { dialogRef .afterClosed() .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(() => { - this.userService - .get(true) - .pipe(takeUntilDestroyed(this.destroyRef)) - .subscribe(); + .subscribe(({ withRefresh } = { withRefresh: false }) => { + if (withRefresh) { + this.userService + .get(true) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe(); - this.fetchActivities(); + this.fetchActivities(); - this.changeDetectorRef.markForCheck(); + this.changeDetectorRef.markForCheck(); + } }); } diff --git a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts index c041b565aa..d3bc34acee 100644 --- a/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/create-or-update-activity-dialog/create-or-update-activity-dialog.component.ts @@ -6,10 +6,7 @@ import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { CreateOrderDto, UpdateOrderDto } from '@ghostfolio/common/dtos'; -import { - getDateFormatString, - getStringOrNull -} from '@ghostfolio/common/helper'; +import { getStringOrNull } from '@ghostfolio/common/helper'; import { AssetClassSelectorOption, LookupItem @@ -41,7 +38,6 @@ import { } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCheckboxModule } from '@angular/material/checkbox'; -import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MAT_DIALOG_DATA, @@ -102,7 +98,6 @@ export class GfCreateOrUpdateActivityDialogComponent { protected currencies: string[] = []; protected currencyOfAssetProfile: string | undefined; protected currentMarketPrice: number | null = null; - protected defaultDateFormat: string; protected defaultLookupItems: LookupItem[] = []; protected hasPermissionToCreateOwnTag: boolean; protected isLoading = false; @@ -119,7 +114,6 @@ export class GfCreateOrUpdateActivityDialogComponent { private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly dataService = inject(DataService); - private readonly dateAdapter = inject>(DateAdapter); private readonly destroyRef = inject(DestroyRef); private readonly dialogRef = inject>(MatDialogRef); @@ -127,7 +121,6 @@ export class GfCreateOrUpdateActivityDialogComponent { private readonly impersonationStorageService = inject( ImpersonationStorageService ); - private locale = inject(MAT_DATE_LOCALE); private readonly userService = inject(UserService); public constructor() { @@ -143,15 +136,11 @@ export class GfCreateOrUpdateActivityDialogComponent { !this.impersonationStorageService.getId() && hasPermission(this.data.user?.permissions, permissions.createOwnTag); - this.locale = this.data.user.settings.locale ?? DEFAULT_LOCALE; this.mode = this.data.activity?.id ? 'update' : 'create'; - this.dateAdapter.setLocale(this.locale); - const { currencies } = this.dataService.fetchInfo(); this.currencies = currencies; - this.defaultDateFormat = getDateFormatString(this.locale); this.dataService .fetchPortfolioHoldings({ diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts index 43a28d8c54..21136af668 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.component.ts @@ -1,5 +1,5 @@ -import { GfFileDropDirective } from '@ghostfolio/client/directives/file-drop/file-drop.directive'; import { ImportActivitiesService } from '@ghostfolio/client/services/import-activities.service'; +import { UserService } from '@ghostfolio/client/services/user/user.service'; import { AI_ACTIVITIES_FILE_MAXIMUM_SIZE, AI_MAX_ACTIVITIES, @@ -22,6 +22,10 @@ import { AssetClass } from '@ghostfolio/prisma/enums'; import { GfActivitiesTableComponent } from '@ghostfolio/ui/activities-table'; import { GfDialogFooterComponent } from '@ghostfolio/ui/dialog-footer'; import { GfDialogHeaderComponent } from '@ghostfolio/ui/dialog-header'; +import { + FileFormat, + GfFileDropAreaComponent +} from '@ghostfolio/ui/file-drop-area'; import { DataService } from '@ghostfolio/ui/services'; import { @@ -66,17 +70,16 @@ import { MatTableDataSource } from '@angular/material/table'; import { MatTooltipModule } from '@angular/material/tooltip'; import { IonIcon } from '@ionic/angular/standalone'; import { addIcons } from 'ionicons'; -import { - cloudUploadOutline, - informationCircleOutline, - warningOutline -} from 'ionicons/icons'; +import { informationCircleOutline, warningOutline } from 'ionicons/icons'; import { isArray, sortBy } from 'lodash-es'; import ms from 'ms'; import { from, switchMap } from 'rxjs'; import { ImportStep } from './enums/import-step'; -import { ImportActivitiesDialogParams } from './interfaces/interfaces'; +import { + ImportActivitiesDialogParams, + ImportActivitiesDialogResult +} from './interfaces/interfaces'; @Component({ changeDetection: ChangeDetectionStrategy.OnPush, @@ -86,7 +89,7 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; GfActivitiesTableComponent, GfDialogFooterComponent, GfDialogHeaderComponent, - GfFileDropDirective, + GfFileDropAreaComponent, IonIcon, MatButtonModule, MatCheckboxModule, @@ -104,14 +107,49 @@ import { ImportActivitiesDialogParams } from './interfaces/interfaces'; templateUrl: 'import-activities-dialog.html' }) export class GfImportActivitiesDialogComponent { - private static readonly FILE_EXTENSIONS_OF_GHOSTFOLIO_AI = [ - 'csv', - 'jpeg', - 'jpg', - 'pdf', - 'png' + private static readonly FILE_FORMAT_CSV: FileFormat = { + extensions: ['csv'], + name: 'CSV', + sampleUrl: + 'https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok/sample.csv' + }; + + private static readonly FILE_FORMAT_JSON: FileFormat = { + extensions: ['json'], + name: 'JSON', + sampleUrl: + 'https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok/sample.json' + }; + + private static readonly FILE_FORMATS: FileFormat[] = [ + GfImportActivitiesDialogComponent.FILE_FORMAT_CSV, + GfImportActivitiesDialogComponent.FILE_FORMAT_JSON ]; + private static readonly FILE_FORMATS_OF_GHOSTFOLIO_AI: FileFormat[] = [ + GfImportActivitiesDialogComponent.FILE_FORMAT_CSV, + { extensions: ['jpeg', 'jpg'], name: 'JPG' }, + { extensions: ['pdf'], name: 'PDF' }, + { extensions: ['png'], name: 'PNG' } + ]; + + private static readonly FILE_EXTENSIONS_OF_GHOSTFOLIO_AI = + GfImportActivitiesDialogComponent.FILE_FORMATS_OF_GHOSTFOLIO_AI.flatMap( + ({ extensions }) => { + return extensions; + } + ); + + private static readonly FILE_FORMATS_WITH_GHOSTFOLIO_AI = sortBy( + [ + ...GfImportActivitiesDialogComponent.FILE_FORMATS_OF_GHOSTFOLIO_AI, + GfImportActivitiesDialogComponent.FILE_FORMAT_JSON + ], + ({ name }) => { + return name; + } + ); + protected readonly assetProfileForm = new FormGroup({ assetProfileIdentifier: new FormControl(null, { validators: [Validators.required] @@ -123,6 +161,7 @@ export class GfImportActivitiesDialogComponent { protected details: any[] = []; protected dialogTitle = $localize`Import Activities`; protected errorMessages: string[] = []; + protected readonly ghostfolioAiLoadingText = $localize`Ghostfolio AI is processing the file...`; protected readonly ghostfolioAiTooltip = $localize`The content of your file will be processed by third-party AI providers. Please remove any personal data before uploading.`; protected readonly hasPermissionToEnableGhostfolioAi = hasPermission( @@ -140,6 +179,7 @@ export class GfImportActivitiesDialogComponent { protected isLoading = false; protected isMaxActivitiesOfGhostfolioAiReached = false; + protected readonly language = document.documentElement.lang; protected readonly maxActivitiesOfGhostfolioAi = AI_MAX_ACTIVITIES; protected mode: 'DIVIDEND'; protected pageIndex = 0; @@ -155,6 +195,7 @@ export class GfImportActivitiesDialogComponent { private accounts: CreateAccountWithBalancesDto[] = []; private activities: Activity[] = []; private assetProfiles: CreateAssetProfileWithMarketDataDto[] = []; + private hasChangedUserSettings = false; private platforms: CreatePlatformDto[] = []; private tags: CreateTagDto[] = []; @@ -162,15 +203,35 @@ export class GfImportActivitiesDialogComponent { private readonly dataService = inject(DataService); private readonly destroyRef = inject(DestroyRef); private readonly dialogRef = - inject>(MatDialogRef); + inject< + MatDialogRef< + GfImportActivitiesDialogComponent, + ImportActivitiesDialogResult + > + >(MatDialogRef); private readonly importActivitiesService = inject(ImportActivitiesService); private readonly snackBar = inject(MatSnackBar); + private readonly userService = inject(UserService); public constructor() { - addIcons({ cloudUploadOutline, informationCircleOutline, warningOutline }); + addIcons({ informationCircleOutline, warningOutline }); + } + + protected get fileFormats() { + return this.isGhostfolioAiEnabled + ? GfImportActivitiesDialogComponent.FILE_FORMATS_WITH_GHOSTFOLIO_AI + : GfImportActivitiesDialogComponent.FILE_FORMATS; } public ngOnInit() { + // No takeUntilDestroyed(), the component is already destroyed here + // The opener refreshes the user if withRefresh is true + this.dialogRef.afterClosed().subscribe((result) => { + if (this.hasChangedUserSettings && !result?.withRefresh) { + this.userService.get(true).subscribe(); + } + }); + if ( this.data?.activityTypes?.length === 1 && this.data?.activityTypes?.[0] === 'DIVIDEND' @@ -215,7 +276,7 @@ export class GfImportActivitiesDialogComponent { } protected onCancel() { - this.dialogRef.close(); + this.dialogRef.close({ withRefresh: false }); } protected async onImportActivities() { @@ -248,25 +309,26 @@ export class GfImportActivitiesDialogComponent { } ); } finally { - this.dialogRef.close(); + this.dialogRef.close({ withRefresh: true }); } } - protected onFilesDropped({ - files, + protected onFileSelected({ + file, stepper }: { - files: FileList; + file: File; stepper: MatStepper; }) { - if (files.length === 0 || this.importStep !== ImportStep.UPLOAD_FILE) { + if (this.importStep !== ImportStep.UPLOAD_FILE) { return; } - this.handleFile({ stepper, file: files[0] }); + this.handleFile({ file, stepper }); } protected onGhostfolioAiChange({ checked }: MatCheckboxChange) { + this.hasChangedUserSettings = true; this.isGhostfolioAiEnabled = checked; this.dataService @@ -328,33 +390,6 @@ export class GfImportActivitiesDialogComponent { aStepper.reset(); } - protected onSelectFile(stepper: MatStepper) { - const input = document.createElement('input'); - - input.accept = this.isGhostfolioAiEnabled - ? [ - 'application/JSON', - ...GfImportActivitiesDialogComponent.FILE_EXTENSIONS_OF_GHOSTFOLIO_AI.map( - (fileExtension) => { - return `.${fileExtension}`; - } - ) - ].join(', ') - : 'application/JSON, .csv'; - input.type = 'file'; - - input.onchange = (event) => { - // Getting the file reference - const file = (event.target as HTMLInputElement).files?.[0]; - - if (file) { - this.handleFile({ file, stepper }); - } - }; - - input.click(); - } - protected updateSelection(activities: Activity[]) { this.selectedActivities = activities.filter(({ error }) => { return !error; diff --git a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html index c68594c7b5..eea156f9c2 100644 --- a/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html +++ b/apps/client/src/app/pages/portfolio/activities/import-activities-dialog/import-activities-dialog.html @@ -74,55 +74,13 @@ } @else {
- @if (isLoading) { -
- - Ghostfolio AI is processing the file... -
- } @else { - - } -

- - The following file formats are supported: - CSV - @if (isGhostfolioAiEnabled) { - , JPG, - } @else { - or - } - JSON - @if (isGhostfolioAiEnabled) { - , PDF - or - PNG - } - -

+ @if ( data.deviceType !== 'mobile' && hasPermissionToEnableGhostfolioAi ) { @@ -136,7 +94,7 @@ AI-assisted import (`/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 5f22f4734e..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'; @@ -75,6 +76,7 @@ import type { LivenessHealthResponse } from './responses/liveness-health-respons import type { LookupResponse } from './responses/lookup-response.interface'; import type { MarketDataOfMarketsResponse } from './responses/market-data-of-markets-response.interface'; import type { OAuthResponse } from './responses/oauth-response.interface'; +import type { PlatformResponse } from './responses/platform-response.interface'; import type { PlatformsResponse } from './responses/platforms-response.interface'; import type { PortfolioDividendsResponse } from './responses/portfolio-dividends-response.interface'; import type { PortfolioHoldingResponse } from './responses/portfolio-holding-response.interface'; @@ -149,6 +151,7 @@ export { DataProviderResponse, DividendsResponse, EnhancedAssetProfile, + ExchangeRateResponse, ExportResponse, Filter, FilterGroup, @@ -170,6 +173,7 @@ export { MarketDataOfMarketsResponse, NullableLineChartItem, OAuthResponse, + PlatformResponse, PlatformsResponse, PortfolioChart, PortfolioDetails, 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; +} 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/account-balances/account-balances.component.ts b/libs/ui/src/lib/account-balances/account-balances.component.ts index 678650b2cb..715b4af0ec 100644 --- a/libs/ui/src/lib/account-balances/account-balances.component.ts +++ b/libs/ui/src/lib/account-balances/account-balances.component.ts @@ -11,7 +11,6 @@ import { Component, EventEmitter, OnChanges, - OnInit, Output, effect, inject, @@ -25,7 +24,6 @@ import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; -import { DateAdapter } from '@angular/material/core'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; @@ -63,7 +61,7 @@ import { GfValueComponent } from '../value'; styleUrls: ['./account-balances.component.scss'], templateUrl: './account-balances.component.html' }) -export class GfAccountBalancesComponent implements OnChanges, OnInit { +export class GfAccountBalancesComponent implements OnChanges { @Output() accountBalanceCreated = new EventEmitter(); @Output() accountBalanceDeleted = new EventEmitter(); @@ -94,7 +92,6 @@ export class GfAccountBalancesComponent implements OnChanges, OnInit { >(); public maxDate = new Date(); - private dateAdapter = inject>(DateAdapter); private notificationService = inject(NotificationService); public constructor() { @@ -112,10 +109,6 @@ export class GfAccountBalancesComponent implements OnChanges, OnInit { }); } - public ngOnInit() { - this.dateAdapter.setLocale(this.locale()); - } - public ngOnChanges() { if (this.accountBalances()) { this.dataSource = new MatTableDataSource(this.accountBalances()); diff --git a/libs/ui/src/lib/entity-logo/entity-logo.component.html b/libs/ui/src/lib/entity-logo/entity-logo.component.html index 5a653b81e6..4c25cb8ae1 100644 --- a/libs/ui/src/lib/entity-logo/entity-logo.component.html +++ b/libs/ui/src/lib/entity-logo/entity-logo.component.html @@ -16,6 +16,7 @@ } @else if (src && !hasError) { +
+ {{ loadingText() }} +
+
+} @else { + +} +

+ + The following file formats are supported: + @for (fileFormatListPart of fileFormatListParts(); track $index) { + @if (fileFormatListPart.sampleUrl) { + {{ + fileFormatListPart.value + }} + } @else { + {{ fileFormatListPart.value }} + } + } + +

diff --git a/libs/ui/src/lib/file-drop-area/file-drop-area.component.scss b/libs/ui/src/lib/file-drop-area/file-drop-area.component.scss new file mode 100644 index 0000000000..ef73bc369a --- /dev/null +++ b/libs/ui/src/lib/file-drop-area/file-drop-area.component.scss @@ -0,0 +1,113 @@ +@property --aura-angle { + inherits: true; + initial-value: 0deg; + syntax: ''; +} + +: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 + rgba( + var(--palette-foreground-divider), + var(--palette-foreground-divider-alpha) + ); + // 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; + color: rgba(var(--palette-primary-500), 1); + } + + .cloud-icon { + 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), + var(--palette-foreground-divider-dark-alpha) + ); + } +} + +@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 new file mode 100644 index 0000000000..5d14f276ab --- /dev/null +++ b/libs/ui/src/lib/file-drop-area/file-drop-area.component.stories.ts @@ -0,0 +1,49 @@ +import type { Meta, StoryObj } from '@storybook/angular'; + +import { GfFileDropAreaComponent } from './file-drop-area.component'; +import { FileFormat } from './interfaces/interfaces'; + +const FILE_FORMAT_CSV: FileFormat = { + extensions: ['csv'], + name: 'CSV', + sampleUrl: + 'https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok/sample.csv' +}; + +const FILE_FORMAT_JSON: FileFormat = { + extensions: ['json'], + name: 'JSON', + sampleUrl: + 'https://github.com/ghostfolio/ghostfolio/blob/main/test/import/ok/sample.json' +}; + +const FILE_FORMATS_WITH_GHOSTFOLIO_AI: FileFormat[] = [ + FILE_FORMAT_CSV, + { extensions: ['jpeg', 'jpg'], name: 'JPG' }, + FILE_FORMAT_JSON, + { extensions: ['pdf'], name: 'PDF' }, + { extensions: ['png'], name: 'PNG' } +]; + +export default { + args: { + loadingText: 'Processing the file...' + }, + component: GfFileDropAreaComponent, + title: 'File Drop Area' +} as Meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + fileFormats: [FILE_FORMAT_CSV, FILE_FORMAT_JSON] + } +}; + +export const Loading: Story = { + args: { + fileFormats: FILE_FORMATS_WITH_GHOSTFOLIO_AI, + isLoading: true + } +}; 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 new file mode 100644 index 0000000000..d6cc328b64 --- /dev/null +++ b/libs/ui/src/lib/file-drop-area/file-drop-area.component.ts @@ -0,0 +1,129 @@ +import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config'; + +import { + ChangeDetectionStrategy, + Component, + computed, + input, + output +} from '@angular/core'; +import { IonIcon } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { cloudUploadOutline } from 'ionicons/icons'; + +import { FileFormat } from './interfaces/interfaces'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + host: { + '(document:paste)': 'onPaste($event)', + class: 'd-flex flex-column' + }, + imports: [IonIcon], + selector: 'gf-file-drop-area', + styleUrls: ['./file-drop-area.component.scss'], + templateUrl: './file-drop-area.component.html' +}) +export class GfFileDropAreaComponent { + public readonly fileFormats = input.required(); + public readonly isLoading = input(false); + public readonly language = input(DEFAULT_LANGUAGE_CODE); + public readonly loadingText = input.required(); + + public readonly fileSelected = output(); + + protected readonly accept = computed(() => { + return this.fileFormats() + .flatMap(({ extensions }) => { + return extensions.map((extension) => { + return `.${extension}`; + }); + }) + .join(', '); + }); + + protected readonly fileFormatListParts = computed(() => { + const fileFormats = this.fileFormats(); + + return new Intl.ListFormat(this.language(), { type: 'disjunction' }) + .formatToParts( + fileFormats.map(({ name }) => { + return name; + }) + ) + .map(({ type, value }) => { + return { + value, + sampleUrl: + type === 'element' + ? fileFormats.find(({ name }) => { + return name === value; + })?.sampleUrl + : undefined + }; + }); + }); + + public constructor() { + addIcons({ cloudUploadOutline }); + } + + protected onDragEnter(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + protected onDragOver(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + } + + protected onDrop(event: DragEvent) { + event.preventDefault(); + event.stopPropagation(); + + if (event.dataTransfer) { + // Prevent the browser's default behavior for handling the file drop + event.dataTransfer.dropEffect = 'copy'; + this.emitFirstFile(event.dataTransfer.files); + } + } + + protected onPaste(event: ClipboardEvent) { + if (this.isLoading()) { + return; + } + + const files = event.clipboardData?.files; + + if (files?.length) { + event.preventDefault(); + this.emitFirstFile(files); + } + } + + protected onSelectFile() { + const fileInputElement = document.createElement('input'); + + fileInputElement.accept = this.accept(); + fileInputElement.type = 'file'; + + fileInputElement.onchange = (event) => { + const files = (event.target as HTMLInputElement).files; + + if (files) { + this.emitFirstFile(files); + } + }; + + fileInputElement.click(); + } + + private emitFirstFile(files: FileList) { + const file = files.item(0); + + if (file) { + this.fileSelected.emit(file); + } + } +} diff --git a/libs/ui/src/lib/file-drop-area/index.ts b/libs/ui/src/lib/file-drop-area/index.ts new file mode 100644 index 0000000000..b5cf734153 --- /dev/null +++ b/libs/ui/src/lib/file-drop-area/index.ts @@ -0,0 +1,2 @@ +export * from './file-drop-area.component'; +export * from './interfaces/interfaces'; diff --git a/libs/ui/src/lib/file-drop-area/interfaces/interfaces.ts b/libs/ui/src/lib/file-drop-area/interfaces/interfaces.ts new file mode 100644 index 0000000000..695a3de654 --- /dev/null +++ b/libs/ui/src/lib/file-drop-area/interfaces/interfaces.ts @@ -0,0 +1,5 @@ +export interface FileFormat { + extensions: string[]; + name: string; + sampleUrl?: string; +} diff --git a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts index 4f8540974f..358dbbbcd4 100644 --- a/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts +++ b/libs/ui/src/lib/historical-market-data-editor/historical-market-data-editor-dialog/historical-market-data-editor-dialog.component.ts @@ -6,7 +6,6 @@ import { Component, CUSTOM_ELEMENTS_SCHEMA, DestroyRef, - OnInit, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -17,7 +16,6 @@ import { Validators } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; -import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MAT_DIALOG_DATA, @@ -50,7 +48,7 @@ import { HistoricalMarketDataEditorDialogParams } from './interfaces/interfaces' styleUrls: ['./historical-market-data-editor-dialog.scss'], templateUrl: 'historical-market-data-editor-dialog.html' }) -export class GfHistoricalMarketDataEditorDialogComponent implements OnInit { +export class GfHistoricalMarketDataEditorDialogComponent { public readonly data = inject(MAT_DIALOG_DATA); @@ -60,23 +58,16 @@ export class GfHistoricalMarketDataEditorDialogComponent implements OnInit { }); private readonly destroyRef = inject(DestroyRef); - private readonly locale = - this.data.user.settings.locale ?? inject(MAT_DATE_LOCALE); public constructor( private adminService: AdminService, private changeDetectorRef: ChangeDetectorRef, private dataService: DataService, - private dateAdapter: DateAdapter, public dialogRef: MatDialogRef ) { addIcons({ calendarClearOutline, refreshOutline }); } - public ngOnInit() { - this.dateAdapter.setLocale(this.locale); - } - public onCancel() { this.dialogRef.close({ withRefresh: false }); } 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' 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..6292f8770a 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 @@ -38,7 +40,6 @@ import { CreateActivityResponse, CreateStripeCheckoutSessionResponse, DataProviderHealthResponse, - DataProviderHistoricalResponse, ExportResponse, Filter, ImportResponse, @@ -46,6 +47,7 @@ import { LookupResponse, MarketDataOfMarketsResponse, OAuthResponse, + PlatformResponse, PlatformsResponse, PortfolioDetails, PortfolioDividendsResponse, @@ -306,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}`); } @@ -366,6 +356,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 +908,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 +951,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); } diff --git a/package-lock.json b/package-lock.json index 56f80767dc..3d4a185e5d 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": { @@ -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", @@ -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", @@ -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": { @@ -12758,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 64c2339d8d..629d43969c 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", @@ -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", @@ -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",