Compare commits

...

1522 Commits
2.61.0 ... main

Author SHA1 Message Date
325556188f Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 14m33s
2025-07-02 00:05:30 -07:00
8b9b1c7f11 Feature/refactor getByKey() in PropertyService (#5065)
* Refactor getByKey() in PropertyService

* Update changelog
2025-07-01 18:28:18 +02:00
2f9023dadd Feature/update locales (#5067)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-01 17:51:57 +02:00
a3c4f84822 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 19m45s
2025-06-30 12:00:26 -07:00
f501c7aeae Release 2.176.0 (#5068) 2025-06-30 20:12:07 +02:00
aca2fe1654 Feature/restructure holding detail dialog (#5058)
* Restructure holding detail dialog

* Update changelog
2025-06-30 20:08:40 +02:00
dcf40367c1 Feature/upgrade prettier to version 3.6.2 (#5057)
* Upgrade prettier to version 3.6.2

* Update changelog
2025-06-30 19:59:37 +02:00
dcedaa4d59 Feature/update locales (#5066)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-30 18:50:24 +02:00
28b11b979d Feature/update locales (#5060)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-30 18:44:36 +02:00
7a97ec75f4 Feature/refactor portfolio service (#5063)
* Refactor portfolio service
2025-06-30 18:43:11 +02:00
91a6d14e24 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 12m42s
2025-06-29 18:00:24 -07:00
d9fb159c6a Feature/integrate Fuse.js in GET holdings endpoint (#5062)
* Integrate Fuse.js in GET holdings endpoint

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-29 21:21:39 +02:00
7b88f7978f Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 12m10s
2025-06-29 12:00:24 -07:00
bd43ea9b6c Feature/dynamically compose public routes in sitemap (#5035)
* Dynamically compose public routes in sitemap

* Update changelog
2025-06-29 17:46:50 +02:00
655601c6a3 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 13m42s
2025-06-29 06:00:24 -07:00
e28ab59673 Feature/introduce fuzzy search for quick links of assistant (#5055)
* Introduce fuzzy search for quick links of assistant

* Update changelog
2025-06-29 09:42:55 +02:00
c8291578b8 update workflow
Some checks failed
Docker image CD / build_and_push (push) Failing after 19m0s
2025-06-29 00:17:32 -07:00
57abe53daf update workflow 2025-06-29 00:16:15 -07:00
0b26f2a624 update workflow
Some checks failed
Docker image CD / build_and_push (push) Failing after 0s
2025-06-29 00:14:12 -07:00
e8c1185812 update
Some checks failed
Docker image CD / build_and_push (push) Failing after 0s
2025-06-29 00:12:04 -07:00
c197622880 Merge branch 'main' of github.com:ghostfolio/ghostfolio 2025-06-29 00:11:28 -07:00
3dc287cfd7 Feature/update locales (#5056)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-29 09:10:05 +02:00
9b4730b731 Feature/improve search results of assistant to only display categories with content (#5053)
* Improve search results

* Update changelog
2025-06-29 08:44:11 +02:00
c437bc2534 Feature/allow user to rotate Security Token (#5016)
* Allow user to rotate Security Token

* Update changelog
2025-06-29 08:43:29 +02:00
433f1686e3 Feature/rename Account to account in Order database schema (#5052)
* Rename Account to account in Order database schema

* Update changelog
2025-06-28 22:30:39 +02:00
842a564bb5 Release 2.175.0 (#5051) 2025-06-28 17:19:25 +02:00
559cac31bd Feature/extend AI service by OpenRouter access (#5025)
* Extend AI service by OpenRouter access

* Update changelog
2025-06-28 17:03:11 +02:00
77d3121f0d Feature/rename Account to account in AccountBalance database schema (#5049)
* Rename Account to account in AccountBalance database schema

* Update changelog
2025-06-28 16:52:16 +02:00
f936c69a7f Feature/refactor scraper in manual service (#5048)
* Refactoring
2025-06-28 16:12:00 +02:00
3fcd611e29 Feature/extend selector handling of scraper for more use cases (#5047)
* Extend selector handling for more use cases

* Update changelog
2025-06-28 15:50:50 +02:00
4983b7ca12 Feature/simplify badge style in admin settings (#5046)
* Simplify badge style
2025-06-28 15:28:24 +02:00
a45ad41459 Feature/update locales (#5044)
* Update locales

* Update translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-28 08:34:15 +02:00
854a08c7a5 Bugfix/fix various routes (#5042)
* Fix various routes
2025-06-27 23:13:55 +02:00
1ec81d352b Feature/restructure import test files (#4977)
* Restructure import test files
2025-06-27 20:54:01 +02:00
5dfe9b54fe Feature/rename Analytics to analytics in User database schema (#5032)
* Rename Analytics to analytics in User database schema
2025-06-27 20:53:29 +02:00
b34ef31812 Feature/improve language localization for CA (#5041)
* Improve language localization for CA
2025-06-27 18:57:29 +02:00
bd04b4a60a Feature/improve language localization for NL (#5040)
* Improve language localization for NL
2025-06-27 18:56:45 +02:00
3de2c9a81c Feature/update locales (#5039)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-27 18:56:07 +02:00
351fd8a1d4 Feature/language localization for static portfolio analysis rule: Currency Cluster Risks (#5038)
* Language localization for static portfolio analysis rule: Currency Cluster Risks

* Update changelog
2025-06-27 17:27:46 +02:00
a919f3bb3b Feature/improve language localization for NL (#5037)
* Improve language localization for NL

* Update changelog
2025-06-27 17:17:29 +02:00
36741dedf0 Feature/update locales (#5034)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-27 17:14:42 +02:00
f0eb0dc7fc Feature/localize asset class cluster risks (Equity and Fixed Income) (#5033)
* Localize asset class cluster risks

* Update changelog
2025-06-26 16:27:57 +02:00
ded0385da7 Feature/change entrypoint.sh to run app with main PID (#5019)
* Change entrypoint.sh to run app with main PID

* Update changelog
2025-06-25 20:06:40 +02:00
0fca29199d Feature/improve language localization for TR 20250625 (#5030)
* Improve language localization for TR

* Update changelog
2025-06-25 18:51:55 +02:00
2fa16480a5 Feature/update locales (#5031)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-25 18:16:16 +02:00
c40993ce33 Feature/improve language localization for ES 20250625 (#5029)
* Improve language localization for ES

* Update changelog
2025-06-25 18:14:27 +02:00
a7a33eeb56 Bugfix/fix locale in scraper configuration (#5011)
* Fix locale in scraper configuration

* Update changelog
2025-06-25 18:08:45 +02:00
a0c5958f81 Feature/improve language localization for CA 20250625 (#5028)
* Improve language localization for CA

* Update changelog
2025-06-25 17:56:28 +02:00
d609b75254 Release 2.174.0 (#5023) 2025-06-24 20:36:27 +02:00
197aea1bec Feature/update locales (#5022)
* Update locales

* Update translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-24 20:34:27 +02:00
bad3f87957 Feature/improve style in admin settings component (#5014)
* Improve style
2025-06-24 19:58:31 +02:00
84c4d79a74 Feature/Set up account cluster risks localization (Current Investment) (#5012)
* Set up localization

* Update changelog
2025-06-24 19:57:59 +02:00
960b871aa5 Feature/add new badge to settings of admin page navigation (#5020)
* Add new badge
2025-06-24 19:56:47 +02:00
24f1aeb4c6 Feature/refresh cryptocurrencies list 20250624 (#5013)
* Update cryptocurrencies.json

* Update changelog
2025-06-24 18:58:49 +02:00
0334eabdad Feature/improve language localization for FR 20250624 (#5015)
* Improve language localization for FR

* Update changelog
2025-06-24 18:57:35 +02:00
d1351b4665 Feature/update locales (#5007)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-23 21:18:55 +02:00
99cab25fe0 Feature/improve labels in data provider status component (#5008)
* Improve labels
2025-06-23 17:38:49 +02:00
738f7490f6 Feature/rename Platform to platform in Account database schema (#4999)
* Rename Platform to platform in Account database schema

* Update changelog
2025-06-23 15:15:04 +02:00
b6854f30e1 Feature/add status column to data providers table (#4998)
* Add status column to data providers table

* Update changelog
2025-06-23 15:12:46 +02:00
be3a9b8e83 Feature/refactor health check endpoints (#5005)
* Refactor health check endpoints

* Update changelog
2025-06-22 20:17:51 +02:00
f33d902560 Feature/migrate value component to control flow (part 2) (#5000)
* Migrate to control flow

* Update changelog
2025-06-22 20:15:51 +02:00
7619772bf6 Feature/update locales (#5004)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-22 20:14:15 +02:00
9bd7f795ac Feature/improve line height on pricing page (#5002)
* Improve line height
2025-06-22 19:52:34 +02:00
4964a8675b Feature/improve localization on pricing page (#5003)
* Improve localization
2025-06-22 19:50:01 +02:00
39fbb3a27c Feature/update locales (#4996)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-21 20:10:23 +02:00
3c427a0005 Feature/update locales (#4995)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-21 19:54:15 +02:00
f5aae11a4c Release 2.173.0 (#4994) 2025-06-21 19:52:15 +02:00
69371bd42f Feature/update locales (#4993)
* Update locales

* Update translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-21 19:50:12 +02:00
38ae224ee1 Feature/upgrade prisma to version 6.10.1 (#4982)
* Upgrade prisma to version 6.10.1

* Update changelog
2025-06-21 19:26:27 +02:00
ee7bb911d7 Feature/simplify data providers management of admin control panel (#4991)
* Set up open color for CSS variables usage

* Simplify data providers management

* Update changelog
2025-06-21 19:26:06 +02:00
dca821e75c Bugfix/resolve LegacyRouteConverter warning on startup (#4980)
* Resolve LegacyRouteConverter warning on startup

* Update changelog
2025-06-21 19:22:36 +02:00
56128d8fe8 Feature/reuse root url in sitemap service (#4989) 2025-06-21 13:57:48 +02:00
e45b1e9b1f Feature/update locales (#4988)
* Update locales

* Update translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-21 13:33:24 +02:00
c8f46f64fb Feature/extend public routes by i18n ids in paths (#4987)
* Add i18n ids
2025-06-21 13:28:22 +02:00
1f50a9bf7b Feature/update locales (#4986)
* Update locales

* Update translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-21 12:56:23 +02:00
314b121584 Feature/refactor sitemap module (part 2) (#4985)
* Refactor sitemap module
2025-06-21 12:50:32 +02:00
ff447b6de6 Feature/update locales (#4984)
* Update locales

* Update translation

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-21 12:12:32 +02:00
29ecfc1137 Feature/migrate assistant and value component to control flow (part 3) (#4981)
* Migrate to control flow

* Update changelog
2025-06-21 11:56:56 +02:00
029504d843 Feature/refactor sitemap module (#4983)
* Refactor sitemap module
2025-06-21 11:56:35 +02:00
d5d74eb4db Feature/rename GranteeUser to granteeUser in Access database schema (#4979)
* Rename GranteeUser to granteeUser in Access database schema

* Update changelog
2025-06-21 09:42:41 +02:00
14b213c571 Bugfix/fix variable resolution in HtmlTemplateMiddleware (#4975)
* Fix variable resolution in HtmlTemplateMiddleware

* Update changelog
2025-06-21 09:27:28 +02:00
463dc3b295 Feature/upgrade class-validator to version 0.14.2 (#4950)
* Upgrade class-validator to version 0.14.2

* Update changelog
2025-06-20 19:40:17 +02:00
28d9bb81be Feature/improve language localization for FR 20250620 (#4972)
* Improve language localization for FR

* Update changelog
2025-06-20 17:22:33 +02:00
26cb9ca11a Release 2.172.0 (#4970) 2025-06-19 22:28:19 +02:00
3aae0aa40c Bugfix/fix migration to form control in value component (#4969)
* Fix migration to form control
2025-06-19 18:52:29 +02:00
f541832666 Feature/update locales (#4968)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-19 18:51:51 +02:00
d858aa53ec Feature/update locales (#4966)
* Update locales

* Update translation

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-19 17:53:20 +02:00
f351212600 Bugfix/fix routing for user account page (#4965)
* Fix routing
2025-06-19 17:52:51 +02:00
7a251ef749 Feature/set up language localization for X-ray rule Account Cluster Risks (Single Account) (#4959)
* Set up language localization

* Update changelog
2025-06-19 17:30:36 +02:00
0b143088a2 Feature/update locales (#4962)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-19 11:00:29 +02:00
6e3cf1460e Feature/improve language localization for PT 20250618 (#4963)
* Improve language localization for PT
2025-06-19 10:53:43 +02:00
46aa2ebc7a Feature/improve language localization for PT (#4961)
* Improve language localization for PT
2025-06-18 21:21:38 +02:00
d8626ffab7 Feature/include admin control panel in quick links of assistant (#4955)
* Include admin control panel in quick links of assistant

* Update changelog
2025-06-18 20:29:56 +02:00
a31f71d7ee Feature/improve language localization for ES 20250614 (#4924)
* Improve language localization for ES

* Update changelog
2025-06-18 20:22:20 +02:00
b92eff5b72 Feature/switch data provider service to OnModuleInit ensuring (currency) quotes are fetched only once (#4944)
* Switch data provider service to OnModuleInit ensuring (currency) quotes are fetched only once

* Update changelog
2025-06-18 20:21:46 +02:00
6f4e0f11cf Feature/extend development guide to start client in other languages (#4881)
* Extend start client guide
2025-06-18 19:47:47 +02:00
a56016ee03 Bugfix/add missing import of entity logo component (#4960)
* Add missing import
2025-06-18 19:45:11 +02:00
33ef272243 Feature/update locales (#4954)
* Update locales

* Clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-16 22:06:55 +02:00
78d8a1c8d4 Feature/update locales (#4952)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-16 20:51:34 +02:00
b0e1065f79 Feature/conditionally display date range options based on first user activity (#4945)
* Conditionally display date range options based on first user activity

* Update changelog
2025-06-16 20:32:25 +02:00
153b162927 Feature/refactor various routes (part 3) (#4951)
* Refactor various routes
2025-06-16 20:20:23 +02:00
32919f96a9 Feature/update locales (#4947)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-16 08:07:41 +02:00
5fde161eed Feature/clean up space in AI service (#4942)
* Clean up space
2025-06-16 08:03:38 +02:00
18f94a1ba1 Feature/refactor various routes (part 2) (#4949)
* Refactor various routes
2025-06-16 08:02:11 +02:00
a7319844fd Feature/migrate assistant and value component to control flow (part 2) (#4948)
* Migrate to control flow

* Update changelog
2025-06-15 22:05:32 +02:00
8ae91961d9 Feature/refactor various routes (#4946)
* Refactor various routes
2025-06-15 21:40:25 +02:00
0cdad51226 Feature/improve language localization for PT 20250614 (#4925)
* Improve language localization for PT

* Update changelog
2025-06-15 17:59:38 +02:00
b63a57cf9e Feature/improve language localization for ZH (#4927)
* Improve language localization for ZH

* Update changelog
2025-06-15 17:47:06 +02:00
ec5b2693c0 Release 2.171.0 (#4941) 2025-06-15 16:48:22 +02:00
da1a6445b1 Feature/update locales (#4939)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-15 15:27:08 +02:00
ed649fe37e Release 2.171.0-beta.4 (#4940) 2025-06-15 15:23:57 +02:00
3ea72af5a0 Bugfix/prevent date offset in cash balance records (#4906)
* Prevent date offset in cash balance records

* Update changelog
2025-06-15 15:19:12 +02:00
ab00ebee1e Bugfix/fix missing assetlinks.json for TWA (part 2) (#4938)
* Fix missing assetlinks.json

* Update changelog
2025-06-15 14:26:54 +02:00
ca8525a90c Bugfix/Google Sign-in hangs on redirect (#4926)
* Exclude routes with language codes
2025-06-15 14:20:28 +02:00
ffd6f51289 Feature/update locales (#4929)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-15 11:05:03 +02:00
4b556dfdc4 Feature/refactor public routes: resources (#4937)
* Refactor public routes: resources
2025-06-15 10:57:10 +02:00
c4cde4eef6 Feature/refactor public sub routes: about (#4935)
* Refactor public sub routes: about
2025-06-15 10:19:17 +02:00
072d700b82 Feature/refactor public routes: markets (#4934)
* Refactor public routes: markets
2025-06-15 10:09:38 +02:00
908a731989 Feature/refactor public routes: faq (#4933)
* Refactor public routes: faq
2025-06-15 09:48:32 +02:00
60c9b5300d Feature/refactor public routes: blog (#4932)
* Refactor public routes: blog
2025-06-15 09:11:41 +02:00
74b51fe1dd Feature/refactor public routes: pricing (#4931)
* Refactor public routes: pricing
2025-06-15 09:04:18 +02:00
1400660664 Feature/refactor tab configuration (#4930)
* Refactor path to routerLink
2025-06-15 08:40:19 +02:00
38756b853d Feature/refactor public routes: oss-friends (#4928)
* Refactor public routes: oss-friends
2025-06-15 08:14:37 +02:00
853a3b12ad Feature/update locales (#4920)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-14 12:58:06 +02:00
c67e351ccc Feature/revert icon on pricing page (#4923)
* Revert icon
2025-06-14 12:52:13 +02:00
1674944596 Feature/improve language localization for PL 20250614 (#4922)
* Improve language localization for PL
2025-06-14 12:44:54 +02:00
c453a8cd07 Feature/improve language localization for ES 20250614 (#4921)
* Improve language localization for ES

* Update changelog
2025-06-14 12:43:20 +02:00
2f7425d0a2 Feature/refactor public routes (#4919)
* Refactor public routes: features
2025-06-14 10:46:55 +02:00
8d4a21c096 Feature/restructure pricing page (#4916)
* Restructure content

* Update changelog
2025-06-14 10:46:26 +02:00
fb3f7517e2 Feature/improve language localization for PT 20250614 (#4918)
* Improve language localization for PT

* Update changelog
2025-06-14 10:45:56 +02:00
279757fab3 Feature/update locales (#4917)
* Update locales

* Update  translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-14 10:27:20 +02:00
b0cf5f7f80 Feature/improve language localization for TR 20250614 (#4914)
* Improve language localization for TR

* Update changelog
2025-06-14 10:07:52 +02:00
be3be20604 Feature/set market state of exchange rate symbols to open in FMP service (#4915)
* Set market state of exchange rate symbols to open

* Update changelog
2025-06-14 10:07:06 +02:00
5af53eb97e Feature/improve language localization for CA 20250614 (#4913)
* Improve language localization for CA
2025-06-14 09:45:28 +02:00
8cedd91561 Feature/upgrade Stripe dependencies 20250613 (#4911)
* Upgrade Stripe dependencies

* Update changelog
2025-06-14 08:51:59 +02:00
9c4fe5fa6b Feature/clean up sitemap.xml (#4909)
* Clean up
2025-06-14 08:51:31 +02:00
9c27c1772f Feature/update locales (#4910)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-13 22:18:26 +02:00
d84e28e058 Feature/replace asset profile count with value component in admin settings (#4825)
* Replace asset profile count with value component in admin settings

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-13 20:12:37 +02:00
54249c708b Feature/update locales (#4908)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-13 19:42:19 +02:00
66ef2c2fec Feature/migrate assistant and value component to control flow (#4905)
* Migrate to control flow

* Update changelog
2025-06-13 19:38:20 +02:00
72a3b8709b Feature/update locales (#4898)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-13 13:38:51 +02:00
3b54fcac60 Feature/improve language localization for IT 20250612 (#4904)
* Improve language localization for IT

* Update changelog
2025-06-13 11:03:09 +02:00
ac4536abb5 Feature/improve language localization for CA 20250612 (#4900)
* Improve language localization for CA

* Update changelog
2025-06-13 10:37:12 +02:00
1a72ef40c2 Feature/improve language localization for DE 20250612 (#4903)
* Update translations

* Update changelog
2025-06-12 22:14:47 +02:00
b80c186c97 Feature/improve styles of assistant (#4901)
* Improve styles

* Update changelog
2025-06-12 20:11:42 +02:00
20070d76bc Feature/rename User to user in database schema (#4899)
* Rename User to user in database schema

* Update changelog
2025-06-12 20:02:16 +02:00
17ef147da7 Feature/add current holdings as default options of symbol search in create or update activity dialog (#4892)
* Add current holdings as default options of symbol search in create or update activity dialog

* Update changelog
2025-06-12 18:40:26 +02:00
076ac1a32f Feature/Rewrite HtmlTemplateMiddleware to use Dependency Injection (#4889)
* Rewrite HtmlTemplateMiddleware to use Dependency Injection

* Update changelog
2025-06-12 18:06:08 +02:00
4e985e4796 Feature/improve language localization for PL (#4897)
* Improve language localization for PL

* Update changelog
2025-06-12 14:41:29 +02:00
c6f25e0fb6 Feature/improve language localization for FR 20250611 (#4896)
* Improve language localization for FR

* Update changelog
2025-06-12 08:02:00 +02:00
f3a598a58e Release 2.170.0 (#4895) 2025-06-11 20:26:30 +02:00
8d3e12c646 Feature/add a skeleton loader to changelog page (#4891)
* Add a skeleton loader to changelog page

* Update changelog
2025-06-11 20:24:07 +02:00
a541ccee1b Feature/upgrade prisma to version 6.9.0 (#4887)
* Upgrade prisma to version 6.9.0

* Update changelog
2025-06-11 20:23:19 +02:00
78e81ec36a Feature/improve language localization for FR 20250611 (#4894)
* Improve language localization for FR

* Update changelog
2025-06-11 14:58:43 +02:00
ef64292cd6 Feature/improve language localization for PT 20250611 (#4893)
* Improve language localization for PT
2025-06-11 14:17:00 +02:00
0f537adf3e Feature/rename ApiKey to apiKeys in User database schema (#4890)
* Rename ApiKey to apiKeys in User database schema

* Update changelog
2025-06-11 12:06:59 +02:00
8bc6b51405 Feature/upgrade @types/lodash to version 4.17.17 (#4818)
* Upgrade @types/lodash to version 4.17.17
2025-06-10 15:38:54 +02:00
8e3480c0ce Feature/extend self-hosting FAQ by additional data providers question (#4885)
* Extend FAQ by additional data providers question

* Update changelog
2025-06-10 15:38:06 +02:00
aba42307da Feature/update locales (#4883)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-09 20:34:07 +02:00
4993d81c80 Feature/restructure paths to routes (part 2) (#4884)
* Restructure paths to routes
2025-06-09 20:30:20 +02:00
ced7f1f206 Feature/extend search in assistant by quick links (#4870)
* Extend search in assistant by quick links

* Update changelog
2025-06-09 19:51:27 +02:00
d44eba9617 Feature/improve language localization for PT 20250527 (#4772)
* Improve language localization for PT

* Update changelog
2025-06-09 14:39:37 +02:00
f9aea95a73 Feature/upgrade zone.js to version 0.15.1 (#4869)
* Upgrade zone.js to version 0.15.1

* Update changelog
2025-06-09 09:52:13 +02:00
6056cf9a6a Feature/upgrade @keyv/redis to version 4.4.0 (#4821)
* Upgrade @keyv/redis to version 4.4.0

* Update changelog
2025-06-09 08:44:30 +02:00
d77295f64c Feature/reuse routes in app component (#4878)
* Reuse routes
2025-06-09 08:43:56 +02:00
376b1416bb Bugfix/restrict date range change permission in Zen mode (#4877)
* Restrict date range change permission in Zen Mode

* Update changelog
2025-06-09 08:03:42 +02:00
2e377044ed Feature/update locales (#4874)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-08 21:15:20 +02:00
0d3a99dd39 Release 2.169.0 (#4873) 2025-06-08 21:08:41 +02:00
c604268507 Bugfix/fix issue in header after paths to routes restructuring (#4872)
* Fix issue in header
2025-06-08 21:06:33 +02:00
3af558c580 Bugfix/handle exception in getKeys() of Redis cache service (#4871)
* Handle exception in getKeys()

* Update changelog
2025-06-08 20:58:47 +02:00
85aa323e84 Feature/improve cache verification in health check endpoint (#4868)
* Improve implementation of isHealthy() without using getKeys()

* Update changelog
2025-06-08 17:27:15 +02:00
c42ffcb2a1 Feature/improve language localization for FR 20250607 (#4860)
* Improve language localization for FR

* Update changelog
2025-06-08 17:26:22 +02:00
fec223070f Feature/update locales (#4866)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-08 13:55:08 +02:00
766d792b10 Feature/restructure paths to routes (#4863)
* Restructure paths to routes
2025-06-08 13:50:52 +02:00
a40c726843 Feature/improve language localization for CA 20250607 (#4856)
* Improve language localization for CA

* Update changelog
2025-06-08 13:50:17 +02:00
ffce1c7208 Feature/improve language localization for PL 20250607 (#4857)
* Improve language localization for PL

* Update changelog
2025-06-08 12:31:35 +02:00
7a0b82b5f0 Feature/update locales (#4864)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-08 09:48:00 +02:00
fa99876dca Feature/rename asset profile icon component to entity logo component (#4861)
* Rename asset profile icon component to entity logo component

* Update changelog
2025-06-08 09:37:23 +02:00
25a755d1bf Feature/rename Account to accounts in User database schema (#4859)
* Rename Account to accounts

* Update changelog
2025-06-08 08:50:06 +02:00
71d56ad789 Feature/move asset profile icon component to @ghostfolio/ui (#4858)
* Move asset profile icon component to @ghostfolio/ui

* Update changelog
2025-06-08 08:39:13 +02:00
edba53be29 Bugfix/fix missing assetlinks.json for TWA (#4855)
* Fix command for copying assetlinks.json

* Update changelog
2025-06-07 21:54:09 +02:00
9b233da4bf Release 2.168.0 (#4854) 2025-06-07 16:55:52 +02:00
3bcb19af14 Feature/migrate i18n service to injectable service (#4829)
* Migrate i18n service to injectable service

* Update changelog
2025-06-07 16:52:48 +02:00
21baabc7f2 Feature/upgrade nestjs to version 11.1.3 (#4852)
* Upgrade nestjs to version 11.1.3

* Update changelog
2025-06-07 16:38:18 +02:00
66e430ab9a Feature/add background gradient to sidebar navigation (#4850)
* Add background gradient to sidebar navigation

* Update changelog
2025-06-07 16:25:20 +02:00
042112faa6 Feature/improve language localization for de 20250607 (#4848)
* Update translation

* Update changelog
2025-06-07 13:43:33 +02:00
b3ec353074 Feature/change interpolation syntax in i18n service (#4849)
* Change interpolation syntax from {var} to ${var}
2025-06-07 13:43:06 +02:00
19b8300a4d Release 2.167.0 (#4847) 2025-06-07 10:52:36 +02:00
1d29005736 Feature/update locales (#4834)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-07 10:50:25 +02:00
e38ce2852c Feature/rename Tag to tags in User database schema (#4846)
* Rename Tag to tags

* Update changelog
2025-06-07 10:37:21 +02:00
31f5c0de88 Bugfix/fix import of empty account balances (#4677)
* Fix import of empty account balances

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-07 10:37:01 +02:00
840805f9df Feature/enable column sorting in benchmark component (#4842)
* Enable column sorting in benchmark component

* Update changelog
2025-06-07 09:04:19 +02:00
e8f185e484 Bugfix/fix issue in annualized performance calculation (#4840)
* Fix issue in annualized performance calculation

* Update changelog
2025-06-07 08:31:55 +02:00
1dd3857618 Refactor getCashPositions() in portfolio service (#4800)
* Refactor getCashPositions()
2025-06-07 08:12:23 +02:00
c318e0019e Feature/improve language localization for TR 20250606 (#4844)
* Improve language localization for TR

* Update changelog
2025-06-06 20:51:33 +02:00
b5abdaae07 Feature/extend GfSymbolAutocompleteComponent by default options (#4563)
* Extend GfSymbolAutocompleteComponent by default options

* Update changelog
2025-06-06 20:50:12 +02:00
f87dc437fd Feature/improve language localization for ES 20250605 (#4833)
* Improve language localization for ES

* Updated Changlog
2025-06-06 20:28:19 +02:00
e2ec635bbf Feature/localize X-ray rule EmergencyFundSetup (#4835)
* Set up language localization for static portfolio analysis rule: Emergency Fund (Set up)

* Update changelog
2025-06-06 07:41:35 +02:00
1f01668a52 Feature/upgrade ng-extract-i18n-merge to version 2.15.1 (#4838)
* Upgrade ng-extract-i18n-merge to version 2.15.1

* Update changelog
2025-06-06 07:38:54 +02:00
7d2bb2116f Bugfix/enable import button in import activities dialog (#4791)
* Enable import button in import activities dialog

* Update changelog
2025-06-05 23:11:52 +02:00
fde8ff4bb6 Feature/localize X-ray rule FeeRatioInitialInvestment (#4779)
* Localize X-ray rule FeeRatioInitialInvestment

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-06-05 19:31:45 +02:00
8cee03f16a Feature/upgrade Nx to version 21.1.2 (#4809)
* Upgrade Nx to version 21.1.2

* Update changelog
2025-06-05 17:38:15 +02:00
5fdd27eca0 Release 2.166.0 (#4830) 2025-06-05 07:59:24 +02:00
85af3db0d8 Bugfix/respect filter by holding when exporting activities (#4824)
* Respect filter by holding when exporting activities

* Update changelog
2025-06-04 20:09:06 +02:00
ccee5b1337 Bugfix/respect filter by holding when deleting activities (#4823)
* Respect filter by holding when deleting activities

* Update changelog
2025-06-04 08:53:17 +02:00
9366aea9d5 Feature/improve style of card components (#4812)
* Improve style of title

* Update changelog
2025-06-03 15:55:16 +02:00
5c617f761a Feature/clean up Dockerfile (#4811)
* Clean up
2025-06-03 13:11:55 +02:00
de56a35197 Feature/update locales (#4819)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-03 08:39:43 +02:00
3efe09d3eb Feature/improve language localization for ES 20250602 (#4814)
* Improve language localization for ES

* Update changelog
2025-06-03 07:52:23 +02:00
a11fef5e23 Bugfix/fix exception with currencies in historical market data editor of admin control panel (#4813)
* Fix exception with currencies

* Update changelog
2025-06-03 07:51:38 +02:00
15deed3032 Feature/upgrade Stripe dependencies 20250529 (#4662)
* Upgrade Stripe dependencies

* Upgrade ngx-stripe to version 19.7.0

* Update changelog
2025-06-02 17:32:43 +02:00
8b8d194383 Feature/clean up legacy demo user id property (#4808)
* Clean up
2025-06-02 14:24:19 +02:00
c59d2701f0 Feature/update locales (#4807)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-01 09:52:52 +02:00
90f2e8101c Feature/improve style of system message (#4806)
* Improve style of system message

* Update changelog
2025-06-01 09:50:08 +02:00
7342e7e5ec Feature/support creating custom tags in create or update activity dialog (#4801)
* Support creating custom tags in create or update activity dialog

* Update changelog
2025-06-01 09:49:37 +02:00
ef8bbf1ae8 Feature/improve language localization for TR 20250531 (#4803)
* Improve language localization for TR

* Update changelog
2025-06-01 09:48:24 +02:00
9757df859f Feature/improve data providers management style of admin control panel (#4804)
* Various style improvements
2025-06-01 09:01:23 +02:00
32baf1946c Feature/improve language localization for UK 20250531 (#4802)
* Improve language localization for UK

* Update changelog
2025-05-31 22:23:53 +02:00
b66e60e9f4 Feature/update locales (#4798)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-05-31 14:23:59 +02:00
cb7326edb3 Release 2.165.0 (#4799) 2025-05-31 11:00:18 +02:00
cb7434a8b2 Feature/sync demo account activities based on tags (#4797)
* Sync demo account activities based on tags

* Update changelog
2025-05-31 10:57:13 +02:00
cf76f54c1c Feature/improve language localization for CA 20250531 (#4796)
* Improve language localization for CA

* Update changelog
2025-05-31 10:56:31 +02:00
fda9cc71f7 Feature/modularize cron service (#4795)
* Modularize cron service

* Update changelog
2025-05-31 10:22:41 +02:00
c553fdf6d4 Bugfix/change investment value to take currency effects into account in holding detail dialog (#4789)
* Change investment value to take currency effects into account

* Update changelog
2025-05-31 10:22:02 +02:00
03b04ac7f0 Feature/update locales (#4792)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-30 19:06:44 +02:00
c6e38cd4ac Feature/refresh cryptocurrencies list 20250529 (#4790)
* Update cryptocurrencies.json

* Update changelog
2025-05-30 19:03:51 +02:00
daedfd6ad6 Feature/various style improvements (#4788)
* Various style improvements
2025-05-30 19:01:06 +02:00
3bfbb31191 Feature/upgrade ng-extract-i18n-merge to version 2.15.0 (#4787)
* Upgrade ng-extract-i18n-merge to version 2.15.0

* Update changelog
2025-05-30 14:25:00 +02:00
824985b464 Feature/improve language localization for PL 20250529 (#4786)
* Improve language localization for PL

* Update changelog
2025-05-30 08:24:29 +02:00
62e304490d Feature/improve language localization for ES 20250529 (#4785)
* Improve language localization for ES

* Update changelog
2025-05-29 19:27:09 +02:00
5fa97a9a40 Feature/upgrade big.js to version 7.0.1 (#4784)
* Upgrade big.js to version 7.0.1

* Update changelog
2025-05-29 17:45:59 +02:00
5db5fd903e Feature/extend FAQ section by performance calculation method (#4773)
* Extend FAQ by performance calculation method

* Update changelog
2025-05-29 17:45:32 +02:00
9e74eec04d Feature/rename orders to activities in Tag database schema (#4783)
* Rename orders to activities in Tag database schema

* Update changelog
2025-05-29 17:44:50 +02:00
9f3079716b Feature/improve language localization for NL 20250528 (#4776)
* Improve language localization for NL

* Update sitemap.xml

* Update changelog
2025-05-29 07:35:17 +02:00
bd2a8e2444 Release 2.164.0 (#4778) 2025-05-28 15:28:30 +02:00
a8e48be9da Feature/improve language localization for NL 20250528 (#4775)
* Improve language localization for NL

* Update changelog
2025-05-28 15:25:05 +02:00
bbb55dbec9 Feature/upgrade yahoo-finance2 to version 3.3.5 (#4777)
* Upgrade yahoo-finance2 to version 3.3.5

* Update changelog
2025-05-28 15:21:39 +02:00
0ab7b98077 Feature/refactor publicly accessible page paths (#4768)
* Refactoring
2025-05-28 12:01:54 +02:00
316d118111 Feature/improve language localization for FR 20250527 (#4766)
* Improve language localization for FR

* Update sitemap.xml

* Update changelog
2025-05-27 21:55:24 +02:00
6c1273dc8a Feature/update locales (#4774)
* Update locales

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-27 21:36:11 +02:00
a594b58d61 Feature/improve language localization for ES 20250526 (#4763)
* Improve language localization for ES

* Update changelog
2025-05-27 21:31:36 +02:00
59abe473f0 Feature/improve language localization PL 20250527 (#4767)
* Improve language localization PL

* Update changelog
2025-05-27 08:25:18 +02:00
8c2f8033d7 Feature/upgrade Node.js from version 20 to 22 (#4680)
* Upgrade to Node.js 22

* Update changelog
2025-05-27 07:38:34 +02:00
7e1f4fd11f Release 2.163.0 (#4760) 2025-05-26 11:58:44 +02:00
9122b674ca Feature/upgrade yahoo-finance2 to version 3.3.4 (#4755)
* Upgrade yahoo-finance2 to version 3.3.4

* Update changelog
2025-05-26 11:56:09 +02:00
f20791d5ad Feature/improve language localization for IT 20250525 (#4750)
* Improve language localization for IT

* Update changelog
2025-05-26 07:47:01 +02:00
502f1c9244 Feature/improve language localization for TR 20250523 (#4738)
* Improve language localization for TR

* Update changelog
2025-05-25 21:04:51 +02:00
1f2d2f8d8a Feature/improve language localization 20250524 (#4747)
* Improve language localization
2025-05-25 21:02:51 +02:00
ffe1021d69 Feature/update locales (#4753)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-05-25 21:01:27 +02:00
afb35f7036 Feature/expose docker repository in GitHub workflow (#4716)
* Expose docker repository in GitHub workflow
2025-05-25 20:29:12 +02:00
13b544c67d Feature/refactor router links (#4752)
* Refactor router links
2025-05-25 20:14:04 +02:00
b8d356a949 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m17s
2025-05-24 16:55:12 -07:00
692147f629 Release 2.162.1 (#4749) 2025-05-24 22:00:12 +02:00
9a75e7d257 Bugfix/fix Redis cache health check (#4748)
* Fix Redis cache health check
2025-05-24 21:58:51 +02:00
d96a346b70 Release 2.162.0 (#4746) 2025-05-24 21:06:50 +02:00
1a247d6e97 Feature/improve handling of schema validation errors in search of Yahoo Finance service (#4744)
* Improve handling of schema validation errors

* Update changelog
2025-05-24 20:51:45 +02:00
90385157d7 Feature/improve Ghostfolio data provider integration (#4743)
* Improve Ghostfolio data provider integration
2025-05-24 20:18:46 +02:00
a8a31c141d Bugfix/fix TransformDataSourceInRequestInterceptor after upgrade to NestJS 11 (#4741)
* Fix TransformDataSourceInRequestInterceptor for Express 5
2025-05-24 16:15:51 +02:00
a1786c95a1 Feature/upgrade prisma to version 6.8.2 (#4740)
* Upgrade prisma to version 6.8.2

* Update changelog
2025-05-24 13:29:05 +02:00
6bdf7b185f Bugfix/text alignment in top holdings component (#4734)
* Fix text alignment of allocation column

* Update changelog
2025-05-24 13:23:14 +02:00
b5bd2bd997 Bugfix/rename snake-case hint to kebab-case in paths (#4737)
* Rename snake-case to kebab-case
2025-05-24 10:36:47 +02:00
71b8121b48 Merge branch 'main' of github.com:ghostfolio/ghostfolio 2025-05-23 23:43:23 -07:00
df11615b2f Feature/improve language localization for PL 20250522 (#4732)
* Improve language localization for PL

* Update changelog
2025-05-23 22:08:28 +02:00
7c378d88af Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m21s
2025-05-23 12:00:31 -07:00
4bffb3107d Bugfix/fix page navigation (#4711)
* Fix page navigation and use paths references
2025-05-23 16:54:09 +02:00
8f3fecc5aa Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 17m4s
2025-05-23 06:00:32 -07:00
ba6503636e Feature/upgrade yahoo-finance2 to version 3.3.3 (#4736)
* Upgrade yahoo-finance2 to version 3.3.3

* Update changelog
2025-05-23 14:31:41 +02:00
6e67520b68 Feature/improve Ghostfolio data provider status check (#4735)
* Improve Ghostfolio data provider status check
2025-05-23 14:31:22 +02:00
0ea588315a Feature/improve symbol lookup results by removing currency from name of cryptocurrencies (#4702)
* Improve symbol lookup results by removing currency from name of cryptocurrencies

* Update changelog
2025-05-23 14:23:42 +02:00
c33aa82bd7 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m14s
2025-05-22 12:00:31 -07:00
3d94b1a873 Bugfix/fix exclude route with wildcard of serve static module (#4733)
* Fix route with wildcard

https://docs.nestjs.com/migration-guide#express-v5
2025-05-22 18:23:21 +02:00
11ca51024a Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m4s
2025-05-21 19:10:30 -07:00
bd2d05b143 Feature/improve language localization for PT 20250512 (#4712)
* Improve language localization for PT

* Update changelog
2025-05-21 20:44:49 +02:00
d4666f778d Feature/remove deprecated endpoints in Ghostfolio controller (#4692)
* Remove deprecated endpoints
2025-05-21 20:04:56 +02:00
243ef2206c Feature/improve language localization for ES 20250517 (#4723)
* Improve language localization for ES

* Update changelog
2025-05-21 20:02:52 +02:00
1cfc691a3e Feature/update locales (#4730)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-05-21 19:40:40 +02:00
fe5d6f702b Feature/add hint about delayed market data to markets overview (#4699)
* Add hint about delayed market data

* Update changelog
2025-05-21 16:48:43 +02:00
24160366b9 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m19s
2025-05-20 18:00:44 -07:00
eed9d157f0 Feature/update locales (#4729)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-20 23:57:37 +02:00
a06872b657 Bugfix/improve show condition of button to fetch current market price (#4700)
* Improve show condition of button to fetch current market price

* Update changelog
2025-05-20 23:53:32 +02:00
f4ef91e3be Feature/upgrade twitter-api-v2 to version 1.23.0 (#4693)
* Upgrade twitter-api-v2 to version 1.23.0

* Update changelog
2025-05-20 23:49:54 +02:00
f361ecc732 Feature/improve language localization for FR 20250520 (#4728)
* Improve language localization for FR

* Update changelog
2025-05-20 23:48:34 +02:00
294a1834b6 Feature/improve language localization for CA 20250513 (#4719)
* Improve language localization for CA
2025-05-20 21:05:02 +02:00
f63ede46b0 Feature/improve language localization for IT 20250513 (#4717)
* Improve language localization for IT
2025-05-20 21:02:37 +02:00
3da83fc42b Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m7s
2025-05-20 06:00:44 -07:00
70a4697f54 Feature/remove deprecated endpoints in admin controller (#4687)
* Remove deprecated endpoints

* Update changelog
2025-05-20 09:22:49 +02:00
d395b195ff Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m18s
2025-05-19 18:00:47 -07:00
c2c628e77c Feature/update locales (#4726)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-19 22:46:41 +01:00
a0f377e8eb Feature/refactor ordersCount to activityCount (#4688)
* Refactor ordersCount to activityCount
2025-05-19 22:27:52 +01:00
85d07c27b6 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m23s
2025-05-18 12:00:44 -07:00
57c43e5815 Feature/upgrade yahoo-finance2 to version 3.3.2 (#4721)
* Upgrade yahoo-finance2 to version 3.3.2

* Update changelog
2025-05-18 20:45:00 +02:00
273ec92316 Feature/update locales (#4724)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-18 20:14:36 +02:00
dcc016633e Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m16s
2025-05-18 06:00:45 -07:00
af79888cd6 Feature/add asset profile count to data providers management of admin control (#4707)
* Extend admin settings columns

* assetProfileCount
* status

* Update changelog
2025-05-18 10:54:53 +01:00
2a934a75ec Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m15s
2025-05-17 12:00:45 -07:00
698d71fb3a Feature/restrict permissions of demo user (#4697)
* Restrict permissions of demo user

* Update changelog
2025-05-17 19:39:48 +02:00
e7bfcabac2 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m52s
2025-05-16 12:00:44 -07:00
ccbf958aa6 Feature/upgrade countup.js to version 2.8.2 (#4708)
* Upgrade countup.js to version 2.8.2

* Update changelog
2025-05-16 20:16:24 +02:00
8abc039fac Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m45s
2025-05-15 12:00:45 -07:00
1697b7e1e0 Feature/remove unused Order model (#4690)
* Remove unused Order model
2025-05-15 15:56:05 +01:00
8358928aaf Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 23m8s
2025-05-14 18:00:45 -07:00
895a6214c7 Feature/update locales (#4718)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-14 23:29:32 +02:00
e97757631b Feature/add missing types of impersonationId in controllers (#4691)
* Add missing types
2025-05-14 22:52:11 +02:00
722da69987 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m10s
2025-05-13 12:00:49 -07:00
a8937fbf04 Feature/improve language localization for NL 20250512 (#4714)
* Improve language localization for NL

* Update changelog
2025-05-13 18:49:46 +01:00
e045811232 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m31s
2025-05-12 00:00:44 -07:00
365318e6e0 Feature/improve localization (#4709)
* Update translations

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-05-12 08:40:33 +02:00
66eb7adbdb Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m31s
2025-05-10 12:00:45 -07:00
6c322522d9 Feature/update locales (#4706)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-10 20:54:48 +02:00
0b7fc7a3b2 Feature/migrate data providers overview to Angular Material table (#4704)
* Migrate data providers overview to Angular Material table

* Update changelog
2025-05-10 20:23:06 +02:00
4adc9dc9b1 Feature/upgrade to node-yahoo-finance2 version 3 (#4695)
* Upgrade node-yahoo-finance2 from version 2.11.3 to 3.3.1

* Update changelog
2025-05-10 18:07:32 +02:00
7aab1c08b0 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m38s
2025-05-10 06:00:44 -07:00
7bf87352c9 Feature/update locales (#4703)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-10 14:53:46 +02:00
2932d57c1e Feature/improve language localization for ZH 20250510 (#4701)
* Improve language localization for ZH 20250510

* Update changelog
2025-05-10 14:50:04 +02:00
3fe5a762eb Feature/extend personal finance tools 20250510 (#4698)
* Extend personal finance tools

* Add Balance Pro
* Add PinkLion
2025-05-10 14:48:33 +02:00
12ebce03af Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m42s
2025-05-09 12:00:45 -07:00
755d85a54b Feature/update locales (#4689)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-09 17:43:33 +02:00
d99217a434 Feature/refactor @Input in portfolio proportion chart component (#4684)
* Refactor @Input in GfPortfolioProportionChartComponent
2025-05-09 17:31:35 +02:00
480709c32a Bugfix/add missing permission guard in create watchlist item endpoint (#4686)
* Add missing permission guard

* Update changelog
2025-05-09 17:30:44 +02:00
b6e8431c53 Feature/clean up unused @Input in top holdings component (#4683)
* Clean up unused input
2025-05-09 16:24:03 +02:00
11629ffd26 Feature/clean up unused interfaces (#4685)
* Clean up unused interfaces
2025-05-09 16:20:02 +02:00
037d3b1a60 Feature/rename Order to activities in User database schema (#4669)
* Rename Order to activities in User database schema

* Update changelog
2025-05-09 16:19:14 +02:00
7af153a161 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m36s
2025-05-09 06:00:45 -07:00
72ccf47526 Feature/update locales (#4678)
* Update locales

* Clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-05-09 12:32:23 +02:00
c81d3ced75 Feature/improve language localization for IT 20250509 (#4681)
* Improve language localization for IT 20250509

* Update changelog
2025-05-09 12:03:47 +02:00
1215803a40 Bugfix/fix ApiKeyStrategy error (#4682)
* Fix ApiKeyStrategy error
2025-05-09 10:48:43 +02:00
92fbf33032 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m41s
2025-05-08 12:00:44 -07:00
aadd9f56a0 Feature/extend admin endpoint by asset profile count per data provider (#4676)
* Extend admin endpoint by asset profile count per data provider

* Update changelog
2025-05-08 17:08:19 +02:00
4237ded93c Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m50s
2025-05-08 00:00:44 -07:00
8e76bd82eb Feature/improve language localization for Catalan (#4675)
* Improve language localization for Catalan

* Update changelog
2025-05-08 08:30:44 +02:00
c393aeeb9a Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m57s
2025-05-07 12:00:44 -07:00
828bd5f172 Feature/upgrade to NestJS 11 (#4270)
* Upgrade to NestJS 11

* Update changelog
2025-05-07 20:34:31 +02:00
edbcd0e225 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m29s
2025-05-06 12:00:45 -07:00
03e27dd233 Feature/update locales (#4670)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-06 18:45:02 +02:00
096699d83b Release 2.161.0 (#4671) 2025-05-06 18:08:25 +02:00
19eff6e814 Bugfix/fix horizontal overflow in table of benchmark component (#4668)
* Fix horizontal overflow

* Update changelog
2025-05-06 18:05:30 +02:00
67db1b0de4 Feature/rename Order to activities in SymbolProfile database schema (#4661)
* Rename Order to activities

* Update changelog
2025-05-06 17:43:46 +02:00
c38dab5ab0 Feature/extend holding endpoint by performances (#4660)
* Extend holding endpoint by performances

* Update changelog
2025-05-06 17:43:03 +02:00
40d3eaa023 Bugfix/fix performance calculation on date of activity when unit price differs from market price (#4650)
* Fix performance calculation on date of activity when unit price differs from market price

* Update changelog
2025-05-06 17:41:04 +02:00
3ec2460bfe Feature/update locales (#4664)
* Update locales

* Clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-05-06 17:38:21 +02:00
aaf507b276 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m41s
2025-05-06 06:00:45 -07:00
307621e103 Feature/improve language localization for TR 20250506 (#4663)
* Improve language localization for TR

* Update changelog
2025-05-06 09:42:33 +02:00
4594f7339b Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m19s
2025-05-04 20:32:35 -07:00
6e9622a2cf Release 2.160.0 (#4659) 2025-05-04 17:16:41 +02:00
28d2fd3877 Bugfix/restore incorrect fee currency conversion (#4645)
* Restore incorrect fee currency conversion

* Update changelog
2025-05-04 17:14:46 +02:00
5b6447b60d Feature/update locales (#4658)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-04 15:58:24 +02:00
d661cdc78f Feature/rename account to accounts in platform database schema (#4656)
* Rename Account to accounts in platform database schema

* Update changelog
2025-05-04 15:55:31 +02:00
ecffb53f07 Feature/extend faq pages (#4655)
* Extend FAQ pages

* Update changelog
2025-05-04 15:54:54 +02:00
b93671c740 Feature/update locales (#4654)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-04 12:03:30 +02:00
620ae023d9 Feature/move watchlist to general availability (#4653)
* Add watchlist to features page

* Move watchlist to general availability

* Update changelog
2025-05-04 11:17:15 +02:00
308dfaa58d Feature/upgrade prisma to version 6.7.0 (#4647)
* Upgrade prisma to version 6.7.0

* Update changelog
2025-05-04 11:16:32 +02:00
e4073608e5 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m26s
2025-05-04 01:46:12 -07:00
3646fb7f77 Feature/refactor portfolio holding response (#4649)
* Refactor portfolio holding response

* maxPrice -> marketPriceMax
* minPrice -> marketPriceMin
* orders -> activities
2025-05-04 09:48:43 +02:00
1bced96460 Feature/deprecate portfolio position endpoints (#4648)
* Deprecate api/v1/portfolio/position endpoints

* Update changelog
2025-05-03 20:47:02 +02:00
3e963228d6 Feature/refactor accounts response interface (#4644)
* Refactor accounts response interface
2025-05-02 19:15:11 +02:00
c3887e2f8e Release 2.159.0 (#4643) 2025-05-02 17:01:39 +02:00
72188374ca Feature/upgrade bootstrap to version 4.6.2 (#4631)
* Upgrade bootstrap to version 4.6.2

* Update changelog
2025-05-02 16:47:18 +02:00
f70d71d5bd Feature/improve watchlist for impersonation mode (#4632)
* Improve watchlist for impersonation mode

* Update changelog
2025-05-02 16:46:43 +02:00
770b322137 Feature/extend watchlist endpoint by name, performances and market condition (#4634)
* Extend watchlist endpoint by name, performances and market condition

* Update changelog
2025-05-02 16:11:24 +02:00
6bb85c4fb8 Bugfix/allow GBp in currency code validation (#4640)
* Allow GBp in currency code validation

* Update changelog
2025-05-02 16:09:27 +02:00
e314efb2e1 Feature/improve language localization for FR 20250501 (#4637)
* Improve french translation

* Update changelog
2025-05-02 08:26:04 +02:00
575615b972 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m24s
2025-04-30 13:16:56 -07:00
2306bc597b Release 2.158.0 (#4633) 2025-04-30 17:53:42 +02:00
ca992db14e Bugfix/save activities with type INTEREST, ITEM and LIABILITY (#4630)
* Save activities with type INTEREST, ITEM and LIABILITY

* Update changelog
2025-04-30 17:51:32 +02:00
8fbdcac66c Feature/rename Order to activities in account database schema (#4577)
* Rename Order to activities

* Update changelog
2025-04-30 17:31:06 +02:00
8df9667979 Feature/update locales (#4629)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-30 16:59:50 +02:00
73f009e43b Feature/extend GUI to delete watchlist item (#4624)
* Extend GUI to delete watchlist item

* Update changelog
2025-04-30 11:00:03 +02:00
d9d8eadbd3 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m43s
2025-04-29 18:24:02 -07:00
afac9daeab Release 2.157.1 (#4627) 2025-04-29 20:58:28 +02:00
d919622932 Bugfix/fix create watchlist item for new asset profile (#4625)
* Fix create watchlist item for new asset profile
2025-04-29 20:56:28 +02:00
a5fe259761 Feature/update locales (#4623)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-29 20:31:53 +02:00
db7d45ecb9 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m34s
2025-04-28 12:00:43 -07:00
82cf4afd7d Release 2.157.0 (#4622) 2025-04-28 19:44:44 +02:00
b90bfc3d6e Feature/extend data providers management of admin control panel (#4615)
* Extend data providers management

* Update changelog
2025-04-28 19:42:39 +02:00
1b5a65d391 Feature/update locales (#4621)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-28 19:28:12 +02:00
34f191ef7a Feature/change column label in benchmark component (#4616)
* Generalize column label

* Update changelog
2025-04-28 18:53:55 +02:00
fe1df8095a Feature/update locales (#4620)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-28 18:53:33 +02:00
7d0af34034 Feature/update locales (#4619)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-28 18:26:26 +02:00
398833a0e3 Feature/improve wording of data providers management (#4617)
* Improve wording
2025-04-28 18:26:07 +02:00
c671ea4022 Feature/add frontend for watchlist (#4604)
* Add frontend for watchlist

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-28 17:17:35 +02:00
909d56ab10 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m20s
2025-04-27 17:44:10 -07:00
456327d199 Release 2.156.0 (#4614) 2025-04-27 14:29:34 +02:00
f209519d95 Bugfix/investment calculation for activities in custom currency (#4597)
* Investment calculation for activities in custom currency

* Update changelog
2025-04-27 14:26:14 +02:00
c34996fdd6 Feature/update locales (#4613)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-27 14:22:52 +02:00
a5e13d30ae Bugfix/fix missing localization for alias fallback on public page (#4610)
* Fix missing localization

* Update changelog
2025-04-27 14:11:37 +02:00
b2dcb1e7ac Feature/improve subscription interstitial (#4612)
* Improve algorithm

* Set up skip button delay
2025-04-27 14:10:57 +02:00
2774dd7b2e Feature/update locales (#4611)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-27 09:50:24 +02:00
862de91e7b Feature/respect watcher count in delete asset profile checkbox (#4609)
* Respect watchedByCount in delete asset profile checkbox

* Update changelog
2025-04-27 09:46:25 +02:00
07fa345457 Feature/upgrade ngx-skeleton-loader to version 11.0.0 (#4602)
* Upgrade ngx-skeleton-loader to version 11.0.0

* Update changelog
2025-04-27 09:44:58 +02:00
68fb5c8b66 Feature/upgrade Nx to version 20.8.1 (#4601)
* Upgrade Nx to version 20.8.1

* Update changelog
2025-04-26 21:47:31 +02:00
d2452791cc Feature/update create watchlist item permission (#4608)
* Remove createWatchlistItem permission
2025-04-26 21:47:03 +02:00
e86801dfe9 Feature/improve language localization for FR 20250426 (#4603)
* Improve french translation

* Update changelog
2025-04-26 17:36:49 +02:00
7fb0f9b6e8 Feature/refactor import service (#4599)
* Refactoring
2025-04-26 07:02:31 +02:00
28dd26be97 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m51s
2025-04-25 08:42:17 -07:00
3b59d7989a Feature/improve currency code validation (#4598)
* Improve currency code validation

* Update changelog
2025-04-25 08:24:16 +02:00
3023d79311 Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Docker image CD / build_and_push (push) Failing after 34m1s
2025-04-24 12:00:44 -07:00
447fe1806f Bugfix/fix activities import of files with extension in uppercase (#4596)
* Fix activities import of files with extension in uppercase

* Update changelog
2025-04-24 20:40:12 +02:00
9f5cc6a4cb Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m21s
2025-04-23 12:00:43 -07:00
4c63e08e3c Release 2.155.0 (#4595) 2025-04-23 20:29:27 +02:00
8dcf04019d Feature/update locales (#4594)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-23 20:27:06 +02:00
ac37974fd6 Feature/update locales (#4593)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-23 20:26:35 +02:00
50e7e3d3c7 Feature/simplify data source checks in DTOs (#4581)
* Simplify DataSource checks in DTOs

* Add test case

* Update changelog
2025-04-23 20:24:08 +02:00
10580e22d1 Feature/migrate value component to control flow (#4592)
* Migrate to control flow

* Update changelog
2025-04-23 20:23:20 +02:00
53a81b3c2b Feature/migrate assistant component to control flow (#4591)
* Migrate to control flow

* Update changelog
2025-04-23 20:15:31 +02:00
56fcafaa12 Feature/improve premium data provider handling in getQuotes() (#4590)
* Improve premium data provider handling in getQuotes()
2025-04-23 20:15:11 +02:00
dfa940c1b4 Bugfix/add missing common module import in rule settings dialog (#4586)
* Add missing import

* Update changelog
2025-04-23 19:46:21 +02:00
d9acd3ace9 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m54s
2025-04-22 12:00:43 -07:00
e67ccea673 Feature/rename User to user in subscription database schema (#4576)
* Rename User to user

* Update changelog
2025-04-22 19:38:03 +02:00
fa2739bbb4 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m54s
2025-04-21 11:24:27 -07:00
416fa44cf0 Feature/create watchlist API endpoints (#4570)
* Create watchlist API endpoints

* Update changelog
2025-04-21 20:19:59 +02:00
b77afed38c Release 2.154.0 (#4583) 2025-04-21 19:49:15 +02:00
ad29221e79 Feature/update locales (#4582)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-21 19:46:58 +02:00
71fc3906c7 Feature/add performance calculation type to user settings (#4567)
* Add performance calculation type to user settings

* Update changelog
2025-04-21 19:28:31 +02:00
26b705cfea Feature/update locales (#4580)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-21 19:08:22 +02:00
3091c3f080 Feature/rename Subscription to subscriptions in user database schema (#4575)
* Rename Subscription to subscriptions

* Update changelog
2025-04-21 16:30:01 +02:00
d6e0b499d9 Feature/migrate lookup by ISIN in Financial Modeling Prep service to stable API version (#4573)
* Migrate lookup by ISIN to stable API version

* Update changelog
2025-04-21 16:29:05 +02:00
1ae5ba7f8a Feature/parallelize asset profile requests in get quotes functionality of Financial Modeling Prep service (#4569)
* Parallelize asset profile requests

* Update changelog
2025-04-21 07:42:32 +02:00
e8e499be26 Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Docker image CD / build_and_push (push) Failing after 35m3s
2025-04-20 18:22:54 -07:00
de6c416639 Feature/update locales (#4574)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-20 16:10:10 +02:00
a41032d0af Feature/add expansion panel for historical market data editor (#4550)
* Add expansion panel for historical market data editor

* Update changelog
2025-04-20 16:06:24 +02:00
b287257a29 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m16s
2025-04-20 00:00:43 -07:00
6b2966b7dc Feature/extend benchmark detail dialog by market price (#4565)
* Add current market price

* Update changelog
2025-04-20 08:01:03 +02:00
c16352b76c Bugfix/fix typos in permissions (#4572)
* Fix typos in permissions
2025-04-20 07:54:34 +02:00
255c3ec5f4 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m26s
2025-04-19 22:11:55 -07:00
e498746c4c Feature/update locales (#4568)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-19 10:22:32 +02:00
8163966212 Bugfix/fix word wrap in menu of historical market data table of admin control panel (#4562)
* Fix word wrap

* Update changelog
2025-04-19 10:18:37 +02:00
1b45ce8619 Feature/add watchlist to user database schema (#4560)
* Add watchlist to user database schema

* Update changelog
2025-04-18 19:25:59 +02:00
f3022ca1f4 Release 2.153.0 (#4561) 2025-04-18 14:44:45 +02:00
b6f87e46a7 Bugfix/fix asset class parsing in Financial Modeling Prep service for exchange rates (#4559)
* Fix the asset class parsing

* Update changelog
2025-04-18 14:41:55 +02:00
f29f201a4f Bugfix/add missing isActive flag in asset profile of custom currency (#4557)
* Add missing isActive flag

* Update changelog
2025-04-18 14:04:39 +02:00
7cebfbc9c2 Feature/update locales (#4556)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-18 11:18:50 +02:00
db38806812 Feature/refresh cryptocurrencies list 20250417 (#4548)
* Update cryptocurrencies.json

* Update changelog
2025-04-18 11:15:51 +02:00
13643f578a Feature/upgrade uuid to version 11.1.0 (#4552)
* Upgrade uuid to version 11.1.0

* Update changelog
2025-04-18 11:14:26 +02:00
edf03d1cd6 Feature/upgrade chart.js to version 4.4.9 (#4547)
* Upgrade chart.js to version 4.4.9

* Update changelog
2025-04-18 11:13:12 +02:00
3361666f63 Feature/activity in custom currency (#4486)
* Activity in custom currency

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-18 11:11:49 +02:00
53122b09ab Feature/update locales (#4554)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-17 20:39:30 +02:00
073e60055d Release 2.152.1 (#4553) 2025-04-17 20:35:12 +02:00
07ad7f2817 Bugfix/fix upgrade vs. renew subscription button labels (#4549)
* Fix upgrade vs. renew subscription button labels
2025-04-17 20:33:11 +02:00
5e64de6650 Feature/extend personal finance tools 20250417 (#4551)
* Add Finvest
* Add Money Peak
* Add Peek
* Add Tresor One
2025-04-17 20:32:40 +02:00
d23dfadbd0 Bugfix/fix active subscription in user table of admin control (#4544)
* Fix active subscription

* Update changelog
2025-04-17 17:47:36 +02:00
c6083ec7c9 Feature/update locales (#4543)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-17 07:49:50 +02:00
535e9b654d Release 2.152.0 (#4542) 2025-04-16 20:59:46 +02:00
94e53c7d4a Feature/move subscription offer from info to user service (#4533)
* Move subscription offer from info to user service

* Update changelog
2025-04-16 20:57:28 +02:00
5072ba09aa Feature/update locales (#4541)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-16 20:51:40 +02:00
6122da3f14 Feature/optimize get range query in market data service (#4527)
* Optimize query in getRange()

* Update changelog
2025-04-16 20:48:43 +02:00
cca1637aec Feature/move API key generation from experimental to general availability (#4540)
* Move API key generation from experimental to general availability
2025-04-16 20:47:30 +02:00
f34fba97ba Feature/upgrade Nx to version 20.8.0 (#4534)
* Upgrade Nx to version 20.8.1

* Update changelog
2025-04-16 20:46:57 +02:00
55791303a0 Feature/upgrade prisma to version 6.6.0 (#4531)
* Upgrade prisma to version 6.6.0

* Update changelog
2025-04-16 19:51:07 +02:00
c86033a0b5 Feature/update locales (#4538)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-16 15:09:50 +02:00
48a0a28d23 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 14m0s
2025-04-16 00:52:36 -07:00
42734dff75 Feature/deactivate asset profile on delisting (Yahoo Finance) (#4524)
* Deactivate asset profile on delisting (Yahoo Finance)

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-16 07:44:42 +02:00
95325aad14 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m50s
2025-04-12 00:47:00 -07:00
b2634db99f Update locales (#4529)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-04-11 19:11:26 +02:00
1856d40ad0 Release 2.151.0 (#4530) 2025-04-11 19:08:56 +02:00
267dfc572a Bugfix/fix pricing link in premium indicator component (#4525)
* Fix link to pricing page

* Update changelog
2025-04-11 19:04:33 +02:00
d1a4cb5037 Feature/improve financial modeling prep service (#4528)
* Improve service

* Set maximum number of symbols per request
* Migrate getQuotes to stable API version

* Update changelog
2025-04-11 19:04:03 +02:00
e6b7073195 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m6s
2025-04-10 19:41:41 -07:00
bb4e034300 Feature/upgrade Nx to version 20.7.1 (#4514)
* Upgrade Nx to version 20.7.1

* Update changelog
2025-04-10 17:45:09 +02:00
6defc02c28 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 36m0s
2025-04-09 19:28:18 -07:00
38e8284aef Feature/update locales (#4526)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-09 21:37:31 +02:00
0f557f431e Feature/add status column to market data table in admin control panel (#4520)
* Add status column to market data table in admin control panel

* Update changelog
2025-04-09 19:33:00 +02:00
587c735288 Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Docker image CD / build_and_push (push) Failing after 47m42s
2025-04-08 19:25:00 -07:00
2c07b7f058 Feature/update locales (#4519)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-08 21:53:20 +02:00
2deca58928 Bugfix/fix action label in asset profile dialog (#4517)
* Fix label

* Update changelog
2025-04-08 08:56:19 +02:00
ed07610602 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m25s
2025-04-06 20:36:56 -07:00
ad47bedcb5 Feature/upgrade eslint dependencies 20250405 (#4513)
* Upgrade eslint dependencies

* Update changelog
2025-04-06 09:33:00 +02:00
830720620f Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m7s
2025-04-05 06:10:04 -07:00
3ec0fe81b4 Release 2.150.0 (#4511) 2025-04-05 11:15:43 +02:00
f7f057f2e8 Feature/update locales (#4509)
* Update locales

* Update translations

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-05 11:13:21 +02:00
57748a18ef Feature/add data gathering toggle to asset profile details dialog (#4497)
* Add data gathering toggle to asset profile details dialog

* Update changelog
2025-04-05 10:52:31 +02:00
d3ecbc0a96 Feature/update locales (#4507)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-05 10:36:50 +02:00
b6fc5566d4 Feature/update messages.pl.xlf (#4499)
* Update messages.pl.xlf

* Update changelog
2025-04-04 22:11:08 +02:00
5754f01819 Feature/improve check for duplicates in preview step of activities import (comments) (#4498)
* Improve check for duplicates in preview step of activities import (comments)

* Update changelog
2025-04-04 22:10:26 +02:00
6ae22d13ec Feature/extend API keys for Ghostfolio data provider (#4501)
* Extend API keys for Ghostfolio data provider
2025-04-04 21:53:35 +02:00
f1d292330a Feature/update locales (#4506)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-04-04 21:51:39 +02:00
2894274b92 Feature/improve language localization for fr (#4502)
* Improve language localization for fr

* Update changelog
2025-04-04 19:37:19 +02:00
29739b27ee Feature/upgrade ng-extract-i18n-merge to version 2.14.3 (#4500)
* Upgrade ng-extract-i18n-merge to version 2.14.3

* Update changelog
2025-04-03 17:20:21 +02:00
a216ba98b6 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m35s
2025-04-02 23:38:10 -07:00
4df575e34f Feature/migrate ion-icon usage to self-closing tags (#4492)
* Migrate to self-closing tags
2025-04-02 16:47:57 +02:00
9c7778983d Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m28s
2025-04-01 11:02:05 -07:00
476b287ef3 Feature/upgrade @types/lodash to version 4.17.16 (#4489)
* Upgrade @types/lodash to version 4.17.16
2025-03-31 12:03:17 +02:00
c58b4c1cda Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 32m44s
2025-03-31 01:56:44 -07:00
1202d15522 Release 2.149.0 (#4495) 2025-03-30 10:35:31 +02:00
8cd7679760 Feature/update locales (#4494)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-30 10:33:27 +02:00
91394160b9 Feature/Allow to edit identifier in asset profile dialog (#4469)
* Allow to edit identifier in asset profile dialog

* Update changelog
2025-03-30 10:26:57 +02:00
64cbd276ce Feature/update locales (#4493)
* Update locales

* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-30 10:15:09 +02:00
b720a8dd96 Feature/set up terms of service (#4490)
* Set up terms of service

* Update changelog
2025-03-30 09:59:56 +02:00
ff563ddfea Feature/upgrade Nx to version 20.6.4 (#4491)
* Upgrade Nx to version 20.6.4

* Update changelog
2025-03-29 21:52:02 +01:00
e46c25aaeb Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m13s
2025-03-27 10:16:26 -07:00
70d56c6c6c Feature/add LinkedIn page to README.md (#4476)
* Add LinkedIn page
2025-03-27 14:23:12 +01:00
a09d3e5beb Feature/harmonize stepper and tab group options (#4455)
* Harmonize stepper options

* Harmonize tab group options
2025-03-26 16:29:42 +01:00
2f8767578c Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Docker image CD / build_and_push (push) Failing after 3h7m58s
2025-03-25 20:06:51 -07:00
a13d6140cf Feature/extend emergency fund X-ray rule to support assets (#4485)
* Extend emergency fund X-ray rule to support assets

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-25 20:54:56 +01:00
31be526c11 Feature/restrict historical market data gathering to active asset profiles (#4483)
* Restrict historical market data gathering to active asset profiles

* Update changelog
2025-03-25 19:53:19 +01:00
f28c452be0 Release 2.148.0 (#4482) 2025-03-24 20:59:18 +01:00
f08ed2dc68 Feature/add isActive flag to asset profile model (#4479)
* Add isActive to SymbolProfile model

* Update changelog
2025-03-24 20:55:41 +01:00
c9e8eb401a Feature/update locales (#4481)
* Update locales

* Update translation

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-24 20:55:11 +01:00
f22c5ee16b Feature/upgrade ngx-skeleton-loader to version 10.0.0 (#4480)
* Upgrade ngx-skeleton-loader to version 10.0.0

* Update changelog
2025-03-24 20:49:52 +01:00
44f6efd427 Feature/extend personal finance tools 20250323 (#4478)
* Add Asseta

* Add Danti
2025-03-24 20:49:24 +01:00
8920b60a72 Bugfix/fix apostrophes in locales (#4475)
* Fix apostrophes
2025-03-23 08:44:08 +01:00
7183db17b0 Release 2.147.0 (#4474) 2025-03-22 17:55:14 +01:00
c467c946d4 Bugfix/fix symbol validation in Yahoo Finance service (#4472)
* Fix symbol validation

* Add test cases
2025-03-22 17:52:16 +01:00
a6952a0e37 Feature/extend get data sources in data provider service (#4473)
* Extend data sources
2025-03-22 17:51:43 +01:00
5247cc3c97 Feature/update locales (#4468)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-22 17:18:40 +01:00
4842c347a9 Feature/generate new security token for user via admin control panel (#4458)
* Generate new security token for user via admin control panel

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-22 10:56:01 +01:00
6c624fefc9 Feature/eliminate firstOrderDate in favor of dateOfFirstActivity in portfolio summary component (#4462)
* Eliminate firstOrderDate in favor of dateOfFirstActivity in portfolio summary component

* Update changelog
2025-03-22 10:21:28 +01:00
5ecdb5fb7a Feature/improve language localization for tr 20250322 (#4467)
* Update translations

* Update changelog
2025-03-22 10:20:30 +01:00
a9c3224856 Feature/add endpoint to localize site.webmanifest (#4450)
* Add endpoint to localize site.webmanifest

* Refactor rootUrl

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-22 09:36:45 +01:00
b5db651ec8 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 33m37s
2025-03-21 21:23:08 -07:00
198f73db00 Feature/improve export by applying filters on accounts and tags (#4425)
* Improve export by applying filters on accounts and tags

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-21 20:58:47 +01:00
536b000ff9 Feature/improve Storybook story of fire calculator (#4451)
* Add default value for fire wealth
2025-03-21 20:41:37 +01:00
150d97bd42 Feature/update locales (#4463)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-21 20:40:59 +01:00
d8b305a343 Feature/add Storybook to sitemap.xml (#4452)
* Add Storybook

* Update changelog
2025-03-20 08:04:02 +01:00
1aa1960d45 Feature/rename TWR to ROAI (#4464)
* Rename TWR to ROAI
2025-03-20 08:03:32 +01:00
22cde840ea Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m18s
2025-03-19 19:08:43 -07:00
795e4582a8 Feature/replace lodash.uniq with Array.from + Set (#4387)
* Replace lodash.uniq with Array.from + Set

* Update chagnelog
2025-03-19 21:10:19 +01:00
ddc7989280 Feature/update locales (#4460)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-19 20:37:30 +01:00
82fe2590bf Feature/add Storybook link to development guide (#4448)
* Add Storybook
2025-03-19 17:26:00 +01:00
ee361bf669 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 34m46s
2025-03-18 20:08:39 -07:00
efc0b1bf5a Feature/support filters in AI prompt API (#4431)
* Support filters in AI prompt API

* Update changelog
2025-03-18 20:13:24 +01:00
235db72ade Bugfix/change client-side dates to be sent in UTC format (#4402)
* Change client-side dates to be sent in UTC format

* Update changelog
2025-03-18 20:06:00 +01:00
170b10dbde Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Docker image CD / build_and_push (push) Failing after 42m5s
2025-03-17 21:13:13 -07:00
4db8c007f0 Bugfix/fix activities import with account balances (#4446)
* Fix import with account balances

* Update changelog
2025-03-17 22:27:22 +01:00
51d55f74e9 Feature/add missing lifecycle hook in historical market data editor dialog (#4456)
* Add OnInit
2025-03-17 21:21:46 +01:00
6036547cf5 Feature/refactor portfolio calculator factory (#4454)
* Refactor portfolio calculator factory
2025-03-17 21:21:09 +01:00
f17a95eb48 Feature/refresh cryptocurrencies list 20250316 (#4449)
* Update cryptocurrencies.json

* Update changelog
2025-03-16 20:59:49 +01:00
6cd79fab31 Feature/rename TWR to ROAI (#4453)
* Rename TWR to ROAI
2025-03-16 20:59:21 +01:00
5783497a66 Feature/improve symbol validation in Yahoo Finance service (#4445)
* Improve symbol validation

* Update changelog
2025-03-16 10:42:35 +01:00
29b8d63951 Release 2.146.0 (#4444) 2025-03-15 19:39:07 +01:00
3efe06409e Feature/improve language localization for de 20250315 (#4442)
* Update translations
2025-03-15 19:36:10 +01:00
0cc3674780 Feature/upgrade prisma to version 6.5.0 (#4440)
* Upgrade prisma to version 6.5.0

* Update changelog
2025-03-15 18:47:33 +01:00
755ab15755 Feature/format name in financial modeling prep service (#4441)
* Format name

* Update changelog
2025-03-15 18:47:07 +01:00
b90e3076d0 Feature/remove exchange rates from admin control overview (#4439)
* Remove exchange rates

* Update changelog
2025-03-15 18:25:15 +01:00
6319661b0c Feature/upgrade prettier to version 3.5.3 (#4435)
* Upgrade prettier to version 3.5.3

* Update changelog
2025-03-15 18:24:05 +01:00
2f868b8902 Feature/add guard to TransformDataSourceInRequestInterceptor (#4438)
* Add guard
2025-03-15 12:22:24 +01:00
1917c17cf9 Bugfix/fix issue with serving Storybook related to contentSecurityPolicy (#4437)
* Fix issue with serving Storybook related to contentSecurityPolicy

* Update changelog
2025-03-15 11:57:01 +01:00
9e44023f86 Feature/improve usability of AI prompt actions (#4426)
* Improve usability of AI prompt actions

* Update changelog
2025-03-15 11:55:48 +01:00
d8dc02fc4a Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 20m41s
2025-03-14 00:00:53 -07:00
a4c78739bb Feature/improve width of user account registration dialog on mobile (#4434)
* Improve width on mobile
2025-03-14 08:00:07 +01:00
7535f3f2b4 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m40s
2025-03-13 18:00:53 -07:00
f528c45c68 Feature/update locales 20250313 (#4433)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-13 20:42:29 +01:00
9a35f1a611 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 22m29s
2025-03-12 12:00:53 -07:00
97037e9481 Feature/upgrade to Nx 20.5 (#4430)
* Upgrade to Nx 20.5

* Update changelog
2025-03-12 14:27:11 +01:00
1eaea2618a Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 32m15s
2025-03-12 06:00:54 -07:00
a137bbbdca Feature/restructure user account registration flow (#4393)
* Restructure user account registration flow

* Update changelog
2025-03-12 13:42:25 +01:00
1cb33a407d Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 34m44s
2025-03-10 18:00:52 -07:00
9abc4af203 Release 2.145.1 (#4422) 2025-03-10 20:47:27 +01:00
70cd1a89b5 Feature/extend Ghostfolio data provider (#4420)
* Extend Ghostfolio data provider
2025-03-10 20:45:41 +01:00
27f5536f23 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 20m22s
2025-03-10 12:00:53 -07:00
feea2ced82 Feature/update certificate for development (#4418)
* Update certificate for development

* Extend documentation
2025-03-10 17:40:38 +01:00
4368beebff Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m2s
2025-03-09 06:00:52 -07:00
7ca0720244 Release 2.145.0 (#4417) 2025-03-09 12:31:11 +01:00
6bdfd8984f Bugfix/fix fetching dividend and historical market data in Financial Modeling Prep service (#4416)
* Add default values

* Update changelog
2025-03-09 12:28:33 +01:00
52081d6741 Bugfix/exclude Storybook from html template middleware (#4414)
* Exclude storybook

* Update changelog
2025-03-09 12:07:23 +01:00
cb1f488eb4 Feature/extend export by account balances (#4390)
* Extend export by account balances

* Update changelog
2025-03-09 10:56:39 +01:00
25d0c1c8a0 Bugfix/fix symbols in API page (#4408)
* Fix symbols
2025-03-09 10:22:32 +01:00
443ff55295 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m48s
2025-03-08 18:00:52 -08:00
d346f6b9fa Bugfix/fix changelog (#4412)
* Update changelog
2025-03-08 21:36:13 +01:00
bdde9dd314 Task/upgrade to simplewebauthn version 13.1 (#4407)
* Upgrade to simplewebauthn version 13.1

* Update changelog
2025-03-08 21:33:47 +01:00
f7185676b0 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 34m15s
2025-03-08 00:00:53 -08:00
9438931849 Update locales (#4411)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-08 08:36:27 +01:00
cd2b26c1c2 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m46s
2025-03-07 18:00:53 -08:00
ff73e7e0ee Feature/adapt style of X-ray rule to summary (#4394)
* Adapt style of X-ray rule to summary

* Update changelog
2025-03-07 21:34:29 +01:00
9278adc6ba Feature/update locales 20250307 (#4409)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-07 21:12:55 +01:00
9db8c5ccef Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m35s
2025-03-07 12:00:54 -08:00
589eefaa76 Feature/extend AI prompt API by mode (#4395)
* Extend AI prompt API by mode

* Update changelog
2025-03-07 19:48:52 +01:00
b260c4f450 Feature/extend personal finance tools 20250306 (#4406)
* Extend personal finance tools

* CoinStats
* Fincake
* Koinly
* Nansen
* Simply Wall St
* Tradervue
2025-03-07 19:48:03 +01:00
44850e0802 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m53s
2025-03-06 00:00:53 -08:00
25320e05d9 Release 2.144.0 (#4403) 2025-03-06 08:14:23 +01:00
72f1246b39 Feature/update locales (#4399)
* Update locales

---

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-06 08:11:56 +01:00
12ae54e01b Bugfix/fix activities import on non-empty activities page (#4398)
* Add missing import functionality

* Update changelog
2025-03-06 06:30:10 +01:00
86a05c0636 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 35m10s
2025-03-05 12:00:52 -08:00
ddb50295b2 Bugfix/fix changelog (#4400)
* Fix changelog
2025-03-05 20:54:32 +01:00
3a09996ca1 Bugfix/fix functionality to delete asset profile of custom currency (#4354)
* Fix functionality to delete asset profile of custom currency

* Update changelog
2025-03-05 20:25:32 +01:00
e1218699bc Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 32m4s
2025-03-04 03:01:07 -08:00
d923e6f2e2 Feature/harmonize section names in bug report template (#4319)
* Harmonize section names
2025-03-03 21:57:39 +01:00
c40aa11827 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 33m50s
2025-03-02 15:17:20 -08:00
533481fc56 Update locales (#4385)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-02 18:08:43 +01:00
f3764f32bb Release 2.143.0 (#4386) 2025-03-02 17:32:09 +01:00
f63d171678 Feature/improve usability of no activities info (#4382)
* Improve usability

* Update changelog
2025-03-02 17:27:45 +01:00
3b68400a23 Feature/upgrade prisma to version 6.4.1 (#4380)
* Upgrade prisma to version 6.4.1

* Update changelog
2025-03-02 17:26:16 +01:00
7def725815 Update locales (#4384)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-03-02 17:23:29 +01:00
5fa7b7a231 Task/upgrade to color version 5.0 (#4383)
* chore(deps): bump color from 4.2.3 to 5.0.0

* chore(deps-dev): remove @types/color

* feat(ui): change color imports

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-03-02 17:19:42 +01:00
51f0ecbfc1 Feature/add LinkedIn page to about page and footer (#4378)
* Add LinkedIn page

* Update changelog
2025-03-02 17:03:11 +01:00
8b353fbf66 Feature/introduce Promise.all() in getPerformance() of portfolio service (#4381)
* Introduce Promise.all()

* Update changelog
2025-03-02 17:01:36 +01:00
8f4f29571b Bugfix/fix export functionality of platforms (#4379)
* Fix export functionality of platforms

* Update changelog
2025-03-02 09:10:51 +01:00
b4656322c7 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 40m57s
2025-03-01 12:36:25 -08:00
6cdcf9622e Bugfix/handle exception in benchmark service related to unnamed asset profiles (#4355)
* Handle exception in benchmark service related to unnamed asset profiles 

* Update changelog
2025-03-01 15:16:21 +01:00
a8d1248461 Feature/improve symbol lookup in Trackinsight service (#4377)
* Improve symbol lookup for EXCHANGE:SYMBOL pattern

* Update changelog
2025-03-01 13:14:39 +01:00
b3954b047f Release 2.142.0 (#4373) 2025-02-28 21:02:20 +01:00
035306d4d8 Feature/add createdAt to snapshot and public portfolio endpoint response (#4350)
* Add createdAt to snapshot and public portfolio endpoint response

* Update changelog

---------

Co-authored-by: Verbindolai <nikolaiwieczorek@web.de>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-28 21:00:02 +01:00
2e6a126686 Feature/upgrade eslint dependencies 20250119 (#4221)
* Upgrade eslint dependencies

* Update changelog
2025-02-28 20:58:50 +01:00
8462b4238f Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 35m35s
2025-02-27 18:14:02 -08:00
2f35f7225f Feature/extend export by platforms (#4360)
* Extend export by platforms

* Update changelog
2025-02-27 20:59:12 +01:00
622393a7cf Feature/add Storybook to build process (#4340)
* Add Storybook to build process

* Update changelog
2025-02-27 20:51:41 +01:00
1b61089475 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 30m13s
2025-02-27 11:46:16 -08:00
168a679535 Feature/extend trackinsight data enhancer by cusip (#4362)
* Extend Trackinsight data enhancer by cusip

* Update changelog
2025-02-27 20:26:51 +01:00
9cc9bee934 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m42s
2025-02-26 09:23:05 -08:00
0349e17a8e Feature/add security policy (#4363)
* Add security policy
2025-02-26 08:03:26 +01:00
c25b45a2b8 Release 2.141.0 (#4361) 2025-02-25 19:51:14 +01:00
8f165d46e0 Feature/add cusip to asset profile model (#4347)
* Add CUSIP

* Update changelog
2025-02-25 19:48:22 +01:00
aa43b674f7 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m43s
2025-02-24 19:27:30 -08:00
634bdf16d6 Feature/extend portfolio snapshot by activities count (#4352)
* Extend portfolio snapshot by activities count

* Update changelog
2025-02-24 21:54:53 +01:00
61db5f24c0 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m12s
2025-02-24 08:07:32 -08:00
190abdf9cc Bugfix/improve numeric comparison of strings in value component (#4330)
* Improve numeric comparison of strings

* Update changelog
2025-02-24 13:53:05 +01:00
301bdc3055 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 31m31s
2025-02-23 21:56:46 -08:00
7577a452f0 Feature/add activities count to GET user endpoint (#4351)
* Add activities count to GET user endpoint

* Update changelog
2025-02-23 08:50:59 +01:00
05b0728b2a Feature/extend export by tags (#4337)
* Extend export by tags

* Update changelog
2025-02-22 09:20:39 +01:00
53ded6e105 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 30m5s
2025-02-22 00:12:55 -08:00
fc3613be4e Feature/upgrade prettier to version 3.5.1 (#4342)
* Upgrade prettier to version 3.5.1

* Update changelog
2025-02-21 20:42:34 +01:00
9c43d9ef44 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 32m23s
2025-02-20 20:09:55 -08:00
c90dca565f Release 2.140.0 (#4345) 2025-02-20 20:53:38 +01:00
f1acff1c76 Feature/update locales (#4333)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-20 20:51:17 +01:00
69a0de28cd Bugfix/fix issue with symbol profile overrides in historical market data table of admin control panel (#4339)
* Respect symbol profile overrides in market data controller (GET)

* Update changelog
2025-02-20 19:40:27 +01:00
78494dd480 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 33m46s
2025-02-20 09:59:05 -08:00
46878ea5a8 Bugfix/improve error handling in http response interceptor (#4338)
* Improve error handling

* Update changelog
2025-02-20 17:46:10 +01:00
ea05e61bd4 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 34m50s
2025-02-19 17:08:34 -08:00
b2698fccbd Feature/improve validation of currency management in admin control panel (#4334)
* Improve validation of currency management

* Update changelog
2025-02-19 17:25:54 +01:00
2413244bf1 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m55s
2025-02-18 13:05:33 -08:00
da79cf406f Feature/reload available tags after creating custom tag in holding detail dialog (#4328)
* Reload user to get available tags after creating custom tag

* Update changelog
2025-02-18 16:56:50 +01:00
57957a7b30 Bugfix/add missing CommonModule in tags selector component (#4335)
* Add missing CommonModule (async pipe)
2025-02-17 21:03:01 +01:00
fe6dcdf682 feature/migrate to Angular control flow syntax (#4321)
* Migrate to Angular control flow syntax

* Update changelog
2025-02-17 19:22:02 +01:00
ec098c8d68 Feature/extend tooltip of treemap chart (#4323)
* feat(ui): Extend tooltip

* allocation
* change
* performance

* feat(sb): add story for treemap chart

* Update changelog
2025-02-17 19:21:08 +01:00
3a97054cae Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 29m12s
2025-02-16 03:00:39 -08:00
02a4e27083 Bugfix/missing assets in Storybook (#4324)
* fix(sb): add staticDirs config

* fix(sb): improve config type safety

* Update changelog
2025-02-16 08:47:44 +01:00
57673046e7 Release 2.139.1 (#4326) 2025-02-15 15:52:25 +01:00
e26b015407 Bugfix/cannot register cron jobs (#4325)
* Reorganize benchmark modules and move benchmarks endpoint
2025-02-15 15:50:41 +01:00
239adc1045 Release 2.139.0 (#4320) 2025-02-15 10:55:37 +01:00
ab9133fa24 Bugfix/fix gaps in chart of benchmark comparator (#4311)
* Fix benchmark interval

* Update changelog
2025-02-15 10:53:06 +01:00
b8c6a73f30 Feature/update locales 20250215 (#4314)
* Update translation

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-15 10:41:15 +01:00
f1ab3ff8e8 Feature/modernize tags endpoint (#4317)
* Modernize tags endpoint

* Update changelog
2025-02-15 10:00:14 +01:00
4f76ee6758 Feature/improve symbol lookup in Trackinsight data enhancer (#4296)
* Feature: enhance Trackinsight data fetching with symbol search fallback

* Feature: refactor Trackinsight symbol search for improved clarity and error handling

* Fixed style concerns and removed code duplication

* Minor improvements

* Update changelog

* Update changelog

* Clean up

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-15 09:43:28 +01:00
1c604c0dbe Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 36m31s
2025-02-15 00:33:12 -08:00
ec8fce44a6 Feature/allow creating custom tags from holding detail dialog component (#4308)
* Allow creating custom tags from holding detail dialog component

* Extend create tag endpoint

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-15 08:48:53 +01:00
58dfba8e63 Feature/upgrade @trivago/prettier-plugin-sort-imports to version 5.2.2 (#4315)
* Upgrade @trivago/prettier-plugin-sort-imports to version 5.2.2

* Update changelog
2025-02-14 20:55:34 +01:00
842a7509d0 Merge branch 'main' of github.com:ghostfolio/ghostfolio 2025-02-12 18:00:07 -08:00
ab379f9abf Feature/extend holding detail dialog by historical market data editor (#4281)
* Extend holding detail dialog by historical market data editor

* Update changelog
2025-02-12 21:21:31 +01:00
ece8c80aa1 Feature/regional market cluster risk for Asia-Pacific (#4313)
* Add regional market cluster risk for Asia-Pacific

* Update changelog
2025-02-12 21:18:35 +01:00
c7f39d3884 Feature/exclude manual data source from encoding (#4307)
* Exclude MANUAL data source from encoding
2025-02-12 21:17:01 +01:00
cda06623fc Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 37m51s
2025-02-11 18:00:08 -08:00
b616274380 Feature/add regional market cluster risk for Japan (#4309)
* Add regional market cluster risk for Japan

* Update changelog
2025-02-11 21:17:30 +01:00
65e4f83c4f Merge branch 'main' of github.com:ghostfolio/ghostfolio 2025-02-09 06:00:07 -08:00
f92c877769 Feature/update locales 20250209 (#4304)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-09 12:13:53 +01:00
35ef06d27a Feature/allow creating custom tags in tag selector component (#4305)
* feat(ui): allow creating custom tags

* feat(ui): add new story

* Update changelog
2025-02-09 10:24:44 +01:00
bbcef36abe Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m12s
2025-02-08 18:00:07 -08:00
56f943824e Feature/introduce readonly attribute in tags selector component (#4301)
* feat(ui): introduce readonly attribute

* Update changelog
2025-02-08 21:20:47 +01:00
7d4339e262 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 19m36s
2025-02-08 12:00:07 -08:00
72d5c713c5 Feature/import global styles in Storybook (#4302)
* feat(ui): include styles in storybook build

* chore(ui): cleanup

* fix(ui): remove styles from storybook target

* Update changelog
2025-02-08 20:58:55 +01:00
e028351015 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m49s
2025-02-08 02:57:33 -08:00
a11e9c7f48 Release 2.138.0 (#4298) 2025-02-08 09:56:39 +01:00
a8d353f29d Feature/extend personal finance tools 20250208 (#4297)
* Add Fey
2025-02-08 09:54:34 +01:00
a7b4b2effe Merge branch 'main' of github.com:ghostfolio/ghostfolio 2025-02-07 12:00:07 -08:00
dee612b2a2 Feature/add regional market cluster risk for emerging markets (#4291)
* Add regional market cluster risk for emerging markets

* Update changelog
2025-02-07 18:08:14 +01:00
e81bd4b51f Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 20m22s
2025-02-07 06:00:07 -08:00
5b786ba143 Feature/improve error handling in CoinGecko service (#4287)
* Improve error handling

* Update changelog
2025-02-07 10:32:25 +01:00
e496c49555 Feature/set up Storybook stories for tags selector component (#4289)
* feat(storybook): create story for tags selector

* Update changelog
2025-02-07 10:28:04 +01:00
cb72c2d93e Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 19m7s
2025-02-05 06:00:08 -08:00
b6c4ef1997 Feature/upgrade svgmap to version 2.12 (#4280)
* chore(deps): bump svgmap from 2.6.0 to 2.12.2

* fix(ui): handle error

* Update changelog
2025-02-05 10:31:22 +01:00
691bdf85a2 update docker image workflow
All checks were successful
Docker image CD / build_and_push (push) Successful in 21m46s
- add cache
- add step to send webhook to rebuild container
2025-02-05 00:09:49 -08:00
b69f578972 Merge branch 'main' of github.com:ghostfolio/ghostfolio
All checks were successful
Docker image CD / build_and_push (push) Successful in 17m9s
2025-02-04 12:00:07 -08:00
8cdef05516 Feature/update locales 20250204 (#4283)
* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-04 20:48:28 +01:00
06298089db Feature/add regional market cluster risk rule for Europe (#4271)
* Add regional market cluster risk rule for Europe

* Update changelog
2025-02-04 19:58:50 +01:00
f638b102da Feature/add link to Duck.ai to copy AI prompt action (#4272)
* Add link to Duck.ai

* Update changelog
2025-02-04 17:41:33 +01:00
5ed14708d4 remove build code workflow 2025-02-04 00:25:54 -08:00
6416485dba update workflows
All checks were successful
Build code / build (20) (push) Successful in 16m23s
Docker image CD / build_and_push (push) Successful in 17m2s
2025-02-04 00:02:39 -08:00
8a986211b0 Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Extract locales / extract_locales (push) Failing after 11m54s
2025-02-03 12:00:07 -08:00
8878212487 Feature/expire snapshot cache on holding tags change (#4277)
* Expire snapshot cache on holding tags change

* Update changelog
2025-02-03 15:23:37 +01:00
738107ca7e Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks are pending
Extract locales / extract_locales (push) Waiting to run
2025-02-02 12:00:07 -08:00
3037f7cec6 Feature/update locales 20250202 (#4275)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-02 20:59:04 +01:00
e6416d5a00 Feature/improve mode value labels in scraper configuration (#4274)
* Improve mode value labels
2025-02-02 16:24:22 +01:00
d711fed4f5 Feature/extract tags selector to reusable component (#4256)
* feat(ui): create gf-tags-selector component

* feat(ui): implement gf-tags-selector in activity dialog

* feat(ui): implement gf-tags-selector in holding detail dialog

* Update changelog
2025-02-02 16:14:35 +01:00
9905c428af Feature/refactor regional market cluster risk rule for North America (#4276)
* Refactoring
2025-02-02 15:25:33 +01:00
6e60cdeae8 Merge branch 'main' of github.com:ghostfolio/ghostfolio 2025-02-02 06:00:08 -08:00
9ab21508a5 Feature/refactor snack bars (#4273)
* Refactor snack bars
2025-02-02 14:48:07 +01:00
14cb0c98ce Feature/update ghostfolio data provider info (#4269)
* Update info
2025-02-02 14:47:34 +01:00
2be43d81d8 merge
Some checks failed
Extract locales / extract_locales (push) Has been cancelled
2025-02-01 14:22:04 -08:00
f708d98710 Release 2.137.1 (#4268) 2025-02-01 16:55:19 +01:00
f09946f72f Release/2.137.0 (#4266)
* Release 2.137.0

* Update changelog
2025-02-01 15:13:02 +01:00
27a2ee5f54 Feature/update locales (#4264)
* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-01 15:10:42 +01:00
e2b27fb6e9 Feature/improve headers label in scraper configuration (#4263)
* Improve label
2025-02-01 13:04:28 +01:00
c095d08816 Feature/update locales 20250201 (#4262)
* Update locales

* Update translations

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-02-01 12:57:44 +01:00
d4f28e2759 Feature/add support for ETF sector data in YahooFinanceDataEnhancer (#4258)
* Add support for ETF sector data in YahooFinanceDataEnhancer

* Update changelog
2025-02-01 12:25:19 +01:00
582355ecb5 Feature/upgrade prisma to version 6.3.0 (#4259)
* Upgrade prisma to version 6.3.0

* Update changelog
2025-01-31 17:21:17 +01:00
a75599bf5d Feature/split scraper configuration into sub form (#4157)
* Split scraper configuration into sub form

* Update changelog
2025-01-31 17:19:50 +01:00
5d2f763ca2 Feature/update locales 20250130 (#4260)
* Update translations

* Update changelog

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2025-01-31 11:13:47 +01:00
0137b57735 Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Extract locales / extract_locales (push) Has been cancelled
2025-01-30 23:39:47 -08:00
8bd869e1b2 Feature/add regional market cluster risk for north america (#4240)
* Add regional market cluster risk for north america

* Update changelog
2025-01-30 22:41:13 +01:00
954cf765b8 Bugfix/dynamic numerical precision for cryptocurrencies in holding detail dialog (#4255)
* fix(ui): dynamic numerical precision of quantity for cryptocurrencies

* Update changelog
2025-01-29 20:22:44 +01:00
fa27a05bcf Feature/upgrade bull to version 4.16.5 (#4218)
* Upgrade bull to version 4.16.5

* Update changelog
2025-01-28 19:32:34 +01:00
7eb5493dd7 Merge branch 'main' of github.com:ghostfolio/ghostfolio
Some checks failed
Extract locales / extract_locales (push) Failing after 16m9s
2025-01-28 10:21:02 -08:00
eb26707e8c Feature/migrate seed.js to TypeScript (#4248)
* feat(db): migrate seed.js to typescript

* feat(db): change seed command

* Update changelog
2025-01-27 20:24:30 +01:00
657cb510d8 Feature/upgrade ng-extract-i18n-merge to version 2.14.1 (#4250)
* Upgrade ng-extract-i18n-merge to version 2.14.1

* Update changelog
2025-01-27 19:52:25 +01:00
38178c774b Feature/upgrade ng-extract-i18n-merge to version 2.14.0 (#4249)
* Upgrade ng-extract-i18n-merge to version 2.14.0

* Update changelog
2025-01-26 19:17:21 +01:00
5ce8a7ab5c Feature/upgrade prettier-plugin-sort-imports to version 5.2.1 (#4217)
* Upgrade @trivago/prettier-plugin-sort-imports to version 5.2.1

* Update changelog
2025-01-25 10:37:31 +01:00
6533f00bae Release 2.136.0 (#4246) 2025-01-24 19:51:01 +01:00
4cb4375514 Bugfix/fix issue with holdings and sectors while using symbol profile overrides (#4234)
* Fix issue with holdings and sectors while using symbol profile overrides

* Update changelog
2025-01-24 19:47:50 +01:00
512b84016c Bugfix/fix issue with detection of thousand separator by locale (#4243)
* Fix issue with detection of thousand separator by locale

* Update changelog
2025-01-24 19:39:24 +01:00
4b65b6277e Feature/refresh cryptocurrencies list 20250123 (#4245)
* Update cryptocurrencies.json

* Update changelog
2025-01-23 21:43:09 +01:00
cca26040bf Feature/extend get asset profile in financial modeling prep service (#4230)
* Extend get asset profile by ETF website
2025-01-23 20:53:50 +01:00
59f0307dcf Feature/set up GitHub action to extract locales (#4239)
* Set up a GitHub action to extract locales when main branch changes

* Update changelog
2025-01-23 20:10:44 +01:00
553c10ac91 Bugfix/fix issue with MIME type detection in scraper configuration (#4237)
* Fix issue with MIME type detection in scraper configuration

* Update changelog
2025-01-22 19:55:09 +01:00
39ac6f352f Feature/extend get historical in financial modeling prep service (#4229)
* Extend get historical

* Update changelog
2025-01-22 19:32:59 +01:00
96fb720cd8 merge 2025-01-21 18:59:42 -08:00
04d7792b8b Feature/upgrade to date-fns version 4 (#4222)
* chore(deps): bump date-fns from 3.6.0 to 4.1.0

* Update changelog
2025-01-21 17:29:21 +01:00
b6f202c94e Feature/upgrade to RxJS 7.8.1 (#4223)
* chore(deps): bump rxjs from 7.5.6 to 7.8.1

* fix(deps): remove rxjs resolutions

* Update changelog
2025-01-20 19:45:23 +01:00
9b98bb07be merge 2025-01-20 08:53:16 -08:00
fc0f2e30c0 Feature/improve language localization for Ukrainian (uk) (#4226)
* Improve language localization for Ukrainian (uk)

* Update changelog
2025-01-20 17:20:14 +01:00
d1688242b0 merge 2025-01-19 22:27:06 -08:00
c72d219246 Release 2.135.0 (#4216) 2025-01-19 10:51:12 +01:00
a932230fb4 Feature/upgrade reflect metadata to version 0.2.2 (#4202)
* Upgrade reflect-metadata to version 0.2.2

* Update changelog
2025-01-19 10:48:49 +01:00
663cee9a05 Feature/change wording in data providers of admin settings (#4207)
* Change wording
2025-01-19 10:39:05 +01:00
7481296e76 Feature/upgrade nx to version 20.3.2 (#4208)
* Upgrade nx to version 20.3.2

* Update changelog
2025-01-19 10:27:11 +01:00
2e4ad7b0ea Feature/move language localization for Polski from experimental to general availability (#4200)
* Move language localization for Polski from experimental to general availability

* Update changelog
2025-01-19 10:15:34 +01:00
6f16e0a650 Feature/upgrade to NestJS 10.4 (#4213)
* chore(deps): bump nestjs packages to latest v10

* chore(deps): bump bull from 4.16.2 to 4.16.4

* chore(deps-dev): bump tslib from 2.6.0 to 2.8.1

* Update changelog
2025-01-19 10:08:45 +01:00
4e51a973c7 Feature/refactor is cryptocurrency check (#4215)
* Refactoring
2025-01-19 09:57:54 +01:00
e3633aaa30 Feature/upgrade UUID to version 11.0.5 (#4201)
* Upgrade uuid to version 11.0.5

* Update changelog
2025-01-19 09:49:00 +01:00
4d201acdbe Feature/extend get dividends in financial modeling prep service (#4210)
* Extend get dividends

* Update changelog
2025-01-19 09:39:05 +01:00
59f84aa46f Feature/upgrade chart.js dependencies to the latest versions (#4209)
* chore(deps): bump chartjs-chart-treemap from 2.3.1 to 3.1.0

* chore(deps): bump chartjs-plugin-annotation from 2.1.2 to 3.1.0

* chore(deps): bump chart.js from 4.2.0 to 4.4.7

* fix(ts): reinforce chart type safety

* Update changelog
2025-01-19 09:32:14 +01:00
511a2d6d0d Feature/extend asset profile data in financial modeling prep service (#4206)
* Extend asset profile data

* Update changelog
2025-01-18 18:48:32 +01:00
40b628e0e7 Feature/extend search by isin in financial modeling prep service (#4204)
* Extend search by ISIN

* Update changelog
2025-01-18 10:50:52 +01:00
75f34101b8 Feature/switch to ESLint flat config format (#4203)
* Switch to ESLint flat config format

* Update changelog
2025-01-18 10:41:16 +01:00
0400cb0551 merge 2025-01-16 22:16:12 -08:00
996f7f3f40 Release 2.134.0 (#4197) 2025-01-15 20:36:14 +01:00
d5e64eaed4 Bugfix/fix issue with import of activities with type FEE (#4187)
* Fix import of activity with type FEE

* Update changelog
2025-01-15 20:34:03 +01:00
e4968dbea7 Feature/extend health check endpoint by database and cache operations (#4188)
* Extend health check endpoint by database and cache operations

* Update changelog
2025-01-15 20:29:43 +01:00
ec79a9efb6 Bugfix/fix exception in scraper configuration (#4196)
* Fix exception in scraper configuration

* Update changelog
2025-01-15 20:28:04 +01:00
9fe19868b9 Feature/set up language localization for Ukrainian (#4190)
* Set up language localization for Ukrainian

* Update changelog
2025-01-15 19:55:03 +01:00
5aad1b4434 Feature/improve language localization for de 20250112 (#4191)
* Update translations

* Update changelog
2025-01-13 17:31:53 +01:00
d7171b9221 Feature/extend promotion system by label (#4181)
* Extend promotion system by label
2025-01-12 11:35:17 +01:00
ffa21edd8e Feature/upgrade prisma to version 6.2.1 (#4184)
* Upgrade prisma to version 6.2.1

* Update changelog
2025-01-11 11:20:56 +01:00
ca45098de3 Feature/refactor various lodash functions with native JavaScript equivalents (#4170)
* Refactored various lodash functions with native JavaScript equivalents

* Update changelog
2025-01-11 11:12:42 +01:00
80bb1b1f64 Bugfix/fix issue with renaming of activities with type FEE, INTEREST, ITEM or LIABILITY (#4183)
* Fix issue with renaming of activities with type FEE, INTEREST, ITEM or LIABILITY

* Update changelog
2025-01-10 19:59:19 +01:00
ce85a14b11 Release 2.133.1 (#4186) 2025-01-09 18:30:21 +01:00
86683a5bf3 Bugfix/fix AI prompt endpoint (#4185) 2025-01-09 18:28:42 +01:00
a593caa94c Release 2.133.0 (#4182) 2025-01-08 19:57:50 +01:00
8c7cb78c0b Feature/add copy AI prompt to clipboard action to analysis page (#4176)
* Add copy AI prompt to clipboard action

* Update changelog
2025-01-08 19:55:23 +01:00
587ce8dd72 merge 2025-01-07 00:28:26 -08:00
68868df41a Bugfix/fix storybook setup (#4168)
* Fix storybook setup

* Update changelog
2025-01-06 22:49:55 +01:00
f6262e2021 merge 2025-01-05 16:47:32 -08:00
a4ee33f6df Feature/upgrade envalid to version 8.0.0 (#4161)
* Upgrade envalid to version 8.0.0

* Update changelog
2025-01-05 12:56:46 +01:00
9259c7b605 Feature/upgrade types of color to version 4.2.0 (#4111)
* Upgrade @types/color to version 4.2.0
2025-01-04 15:45:42 +01:00
2e72ac7faf Feature/format quotes in info service urls (#4174)
* Format quotes
2025-01-03 13:36:26 +01:00
b1340a96e5 Feature/upgrade replace-in-file to version 8.3.0 (#4167)
* Upgrade replace-in-file to version 8.3.0

* Update changelog
2025-01-02 10:50:41 +01:00
02681cc479 Feature/add snack bar to copy link to clipboard action in access table (#4175)
* Add snack bar

* Update changelog
2025-01-02 10:21:58 +01:00
87f6357d74 Feature/send original MIME type in logo endpoint (#4173)
* Send original MIME type in logo endpoint

* Update changelog
2025-01-02 10:09:40 +01:00
bbbd974be6 Bugfix/improve handling of missing url in logo service (#4171)
* Improve handling of missing url

* Update changelog
2025-01-02 06:35:11 +01:00
1cf7ffdee8 Feature/refresh cryptocurrencies list 20241230 (#4165)
* Update cryptocurrencies.json

* Update changelog
2025-01-01 19:20:27 +01:00
b9917e72b2 Feature/update year to 2025 (#4169)
* Update year
2025-01-01 09:19:31 +01:00
7a602ea2d6 Feature/remove got in favor of using fetch (#4154)
* Remove got in favor of using fetch

* Update changelog
2025-01-01 09:16:40 +01:00
aca4c3d46d Feature/modernize docker compose files (#4101)
* Modernize docker compose files

* Update changelog
2024-12-31 08:33:36 +01:00
ee6b723ba5 Feature/update OSS friends list 20241230 (#4166)
* Update OSS friends list
2024-12-31 07:51:39 +01:00
f410ca775d Feature/improve language localization for de 20241230 (#4162)
* Update  translations

* Update changelog
2024-12-30 17:18:33 +01:00
189808f9bf Release 2.132.0 (#4160) 2024-12-30 11:28:35 +01:00
96112955ff Feature/upgrade prisma to version 6.1.0 (#4135)
* Upgrade prisma to version 6.1.0

* Update changelog
2024-12-30 11:24:40 +01:00
167abe4107 Bugfix/fix algebraic sign in twitter bot service (#4158)
* Fix -0.0 to 0.0

* Update changelog
2024-12-30 11:23:55 +01:00
8fc9d1c75c Feature/clean up imports of holdings table component (#4159)
* Clean up imports
2024-12-30 11:23:26 +01:00
3f84caa9f6 Feature/migrate setting Ghostfolio API key prompt dialog (#4151)
* Migrate setting Ghostfolio API key prompt dialog
2024-12-30 10:48:28 +01:00
74bc8222d6 Feature/Refactored got calls to use AbortSignal.timeout() without AbortController() (#4153)
* Feature/refactor got calls to use AbortSignal.timeout

Instead of manually creating AbortController and controlling the abort
with setTimeout.

Feature available since node v16.14.0 and v17.3.0[^1] and is built to
replace the exact scenario that all these requests have.

[^1]:https://nodejs.org/docs/latest-v22.x/api/globals.html#static-method-abortsignaltimeoutdelay

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-12-30 10:46:53 +01:00
ff7caf9c5c Feature/migrate coupon redemption prompt dialog (#4150)
* Migrate coupon redemption prompt dialog

* Update changelog
2024-12-30 10:21:11 +01:00
28f7781fa2 Feature/eliminate body-parser (#4155)
* Eliminate body-parser in favor of using @nestjs/platform-express

* Update changelog
2024-12-30 09:36:04 +01:00
46cbbd3bc4 Feature/upgrade to nx 20.3 (#4152)
* Upgrade to Nx 20.3

* Update changelog
2024-12-29 19:47:32 +01:00
615278a887 Feature/improve automatic deletion of unused asset profiles (#4149)
* Improve automatic deletion of unused asset profiles

* Update changelog
2024-12-29 19:44:14 +01:00
81f874bbc2 Feature/improve language localization for de 20241227 (#4148)
* Update translations

* Update changelog
2024-12-28 09:54:39 +01:00
15639cb3d0 Feature/move set Ghostfolio API key to experimental (#4147)
* Move set Ghostfolio API key to experimental
2024-12-28 09:54:05 +01:00
c9047e7c17 Feature/add user interface for received access (#4146)
* Add user interface for received access

* Update changelog
2024-12-27 10:12:45 +01:00
38908f0e19 Feature/upgrade husky to version 9.1.7 (#4134)
* Upgrade husky to version 9.1.7

* Update changelog
2024-12-26 10:40:15 +01:00
be4ac17a5c Release 2.131.0 (#4143) 2024-12-25 17:08:06 +01:00
f2638614d4 Feature/improve search for asset profiles with manual data source in create or update activity dialog (#4142)
* Improve search for asset profiles with MANUAL data source

* Update changelog
2024-12-25 17:05:57 +01:00
9a579dd884 Feature/change access tab icon on account page (#4137)
* Change icon

* Update changelog
2024-12-25 09:50:27 +01:00
bb04a3a74f Feature/extend personal finance tools 20241222 (#4136)
* Morningstar® Portfolio Manager
* Stock Rover
* Wealthbrain
2024-12-25 09:50:09 +01:00
08a9202dea Feature/improve language localization for Polish (#4141)
* Improve language localization for Polish

* Update changelog
2024-12-24 13:55:59 +01:00
7a19fc53b0 Feature/improve activities import by isin for yahoo finance (#4140)
* Allow activities import by isin even if multiple quotes found

* Update changelog
2024-12-24 13:30:43 +01:00
c5fffed795 Release 2.130.0 (#4133) 2024-12-21 09:19:02 +01:00
09c186f560 Feature/improve language localization for de 20241220 (#4131)
* Update translations

* Update changelog
2024-12-21 09:16:22 +01:00
7fadbd79ea Feature/improve language localization for pl (#4132)
* Improve language localization for Polish

* Update changelog
2024-12-21 09:07:26 +01:00
46422f731e Feature/set up a notification service for prompt dialogs (#4117)
* Set up a notification service for prompt dialogs

* Update changelog
2024-12-19 20:39:36 +01:00
6d8240dfed Feature/upgrade big.js to version 6.2.2 (#4126)
* Upgrade big.js to version 6.2.2

* Update changelog
2024-12-18 14:04:52 +01:00
6ee6121317 Feature/set up asset class cluster risk x-ray rule (#4128)
* Set up Asset Class Cluster Risk rule (equity)

* Set up Asset Class Cluster Risk rule (fixed income)

* Update changelog
2024-12-17 20:33:57 +01:00
c3bd433ac9 Feature/move market data management from admin to dedicated endpoint (#4125)
* Move market data management from admin to dedicated endpoint

* Update changelog
2024-12-15 20:18:42 +01:00
a776ea8864 Release 2.129.0 (#4124) 2024-12-14 11:40:17 +01:00
943ff2f0dc Feature/improve usability of X-ray page by hiding empty rule categories (#4108)
* Hide empty rule categories

* Update changelog
2024-12-14 11:37:39 +01:00
6ad9661d7f Feature/improve language localization for de 20241214 (#4123)
* Update translations

* Update changelog
2024-12-14 11:19:15 +01:00
de68841843 Feature/add user id to symbol profile database schema (#4122)
* Add userId to SymbolProfile database schema

* Update changelog
2024-12-14 10:43:35 +01:00
57659d2c04 Feature/add missing public keywords (#4102)
* Add public keyword
2024-12-13 17:13:51 +01:00
0bd1a94a7b Release 2.128.0 (#4120) 2024-12-12 19:18:38 +01:00
bc3535946c Bugfix/do not fetch holdings in assistant for each change (#4118)
* Do not fetch holdings in assistant for each change

* Update changelog
2024-12-12 19:16:41 +01:00
f3712b293c Bugfix/fix exception in portfolio calculator (#4119)
* Add guard

* Update changelog
2024-12-12 18:29:28 +01:00
6341a6de6a Feature/refactor X-ray summary (#4116)
* Refactor report statistics
2024-12-12 15:23:13 +01:00
41eb9c56dd Feature/improve language localization for de 20241207 (#4106)
* Update translations

* Update changelog
2024-12-11 14:38:23 +01:00
Ed
febb4119c6 Bugfix/fix jsonpath import (#4115)
* Fix jsonpath import

* Update changelog
2024-12-10 22:04:15 +01:00
801030ce64 Feature/upgrade internationalized number to version 3.6.0 (#4112)
* Upgrade @internationalized/number to version 3.6.0

* Refactoring

* Update changelog
2024-12-10 14:37:44 +01:00
618a918423 Release 2.127.0 (#4114) 2024-12-08 18:59:33 +01:00
0841f8bd5b Bugfix/fix exception in portfolio calculator (#4113)
* Fix exception in portfolio calculator

* Update changelog
2024-12-08 18:57:22 +01:00
291be3e605 Feature/extend X-ray page by summary (#4107)
* Add summary to X-ray page

* Update changelog
2024-12-08 08:05:08 +01:00
d6357487ea Release 2.126.1 (#4110) 2024-12-07 21:43:30 +01:00
ebef361d63 Bugfix/fix exception in portfolio calculator (#4109)
* Fix exception in portfolio calculator

* Update changelog
2024-12-07 21:41:21 +01:00
17ffb29275 Release 2.126.0 (#4105) 2024-12-07 15:54:29 +01:00
758a52087d Feature/upgrade prisma to version 6.0.1 (#4104)
* Upgrade prisma to version 6.0.1

* Update changelog
2024-12-07 15:51:55 +01:00
0e01674552 Feature/set hashedKey of ApiKey to unique (#4103)
* Set hashedKey to unique
2024-12-07 15:44:53 +01:00
1be0a64417 Feature/upgrade prettier to version 3.4.2 (#4090)
* Upgrade prettier to version 3.4.2

* Update changelog
2024-12-07 15:32:47 +01:00
13582afd93 Feature/Setup API keys for Ghostfolio data provider (#4093)
* Setup API keys for Ghostfolio data provider
2024-12-07 15:13:12 +01:00
45095cfac0 Feature/expire cache entries immediately in case of errors in portfolio snapshot calculation (#4099)
* Expire cache entries immediately in case of errors

* Update changelog
2024-12-07 10:12:52 +01:00
fea90876bb merge 2024-12-06 00:28:05 -08:00
73be7f3969 Feature/improve labels of assistant (#4091)
* Improve labels

* Update changelog
2024-12-06 09:01:46 +01:00
f97075d82b update 2024-12-04 22:19:54 -08:00
11d5f36c31 Feature/extract historical market data editor to reusable component (#4080)
* Extract historical market data editor to reusable component

* Update changelog
2024-12-04 19:50:22 +01:00
c85a1be3cf Feature/add pagination to users table (#4092)
* Add pagination to users table

* Update changelog
2024-12-03 20:13:45 +01:00
dbfac54af9 Feature/refactor community language method (#4071)
* Refactoring
2024-12-02 17:32:24 +01:00
66cace0c56 Release 2.125.0 (#4089) 2024-11-30 19:17:10 +01:00
9461b32a39 Feature/setup api keys for Ghostfolio data provider (#4088)
* Setup API keys for Ghostfolio data provider
2024-11-30 19:14:15 +01:00
ea24a618b0 Feature/upgrade prisma to version 6 schema migration (#4087)
* Upgrade prisma to version 6 schema migration
2024-11-30 19:13:09 +01:00
3194ed2145 Feature/improve usability of Ghostfolio data provider (#4086)
* Improve usability
2024-11-30 17:30:49 +01:00
11a32a75c6 Feature/upgrade prisma to version 6 (#4082)
* Upgrade prisma to version 6

* Update changelog
2024-11-30 17:27:47 +01:00
d8ab48efc2 Feature/update OSS friends (#4078)
* Update OSS friends
2024-11-30 11:52:13 +01:00
2067e8ea40 Feature/add support for dividends in Ghostfolio data provider (#4081)
* Add support for dividends
2024-11-30 11:49:24 +01:00
c6525ec0f4 Feature/refresh cryptocurrencies list 20241127 (#4077)
* Update cryptocurrencies.json

* Update changelog
2024-11-29 21:08:19 +01:00
66bbbc2cb8 Feature/improve error handling in Ghostfolio data provider (#4079)
* Improve error handling
2024-11-29 20:21:46 +01:00
14eeec8f4f Feature/upgrade cheerio to version 1.0.0 (#4060)
* Upgrade cheerio to version 1.0.0

* Update changelog
2024-11-28 20:38:31 +01:00
8fe767a4e3 Feature/increase default request timeout (#4075)
* Increase default request timeout

* Update changelog
2024-11-28 20:19:37 +01:00
4e368e09a6 Feature/improve style of symbol autocomplete component (#4070)
* Improve style

* Update changelog
2024-11-27 17:44:59 +01:00
fc7e350cf7 Feature/extend users table of admin control panel (#4076)
* Extend users table

* Update changelog
2024-11-27 08:10:20 +01:00
e4e05fac94 Release 2.124.1 (#4072) 2024-11-25 21:28:36 +01:00
9ca901f6e6 Feature/improve api key management of ghostfolio data provider (#4069)
* Improve API key management

* Add fallback for language code
2024-11-25 21:25:57 +01:00
cc88bd9069 Bugfix/fix background colors of sticky columns (#4068)
* Fix background colors of sticky columns

* Update changelog
2024-11-25 21:13:22 +01:00
18de790f36 Release 2.124.0 (#4066) 2024-11-24 19:52:07 +01:00
5f98dfa5d6 Feature/set up Ghostfolio data provider (#4016)
* Set up Ghostfolio data provider

* Update translations

* Update changelog
2024-11-24 19:49:08 +01:00
0bc52fd80e Feature/harmonize log messages in data provider services (#4064)
* Harmonize log messages
2024-11-24 08:59:09 +01:00
a8ea8a4599 Feature/refactor types in EOD historical data service (#4063)
* Refactor types
2024-11-23 20:35:51 +01:00
707c77f0cf Feature/extend allocations by ETF holding with parent ETFs (#4044)
* Extend allocations by ETF holding with parent ETFs

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-11-23 10:25:07 +01:00
6d440eb777 Feature/add count to admin user endpoint response (#4058)
* Add count to admin user endpoint response

* Update changelog
2024-11-20 20:20:11 +01:00
c9693d2d58 Feature/upgrade countries and timezones to version 3.7.2 (#4021)
* Upgrade countries-and-timezones to version 3.7.2

* Update changelog
2024-11-19 14:01:38 +01:00
2568fe4828 Feature/upgrade nx to version 20.1.2 (#4054)
* Upgrade Nx to version 20.1.2

* Update changelog
2024-11-18 12:00:09 +01:00
e7356dc170 Feature/add pagination support to user endpoint (#4050)
* Add pagination support to user endpoint

* Update changelog
2024-11-17 10:05:59 +01:00
4b0e75b26c Release/2.123.0 (#4052)
* Release 2.123.0

* Update changelog
2024-11-16 18:46:37 +01:00
b1b6ea1c24 Feature/improve language localization for de 20241116 (#4051)
* Update translations

* Update changelog
2024-11-16 18:44:04 +01:00
0e7482938b Feature/add black weeks 2024 blog post (#4049)
* Add Black Weeks 2024 blog post

* Update changelog
2024-11-16 18:21:22 +01:00
9d8f116dd1 Feature/upgrade prisma to version 5.22.0 (#4047)
* Upgrade prisma to version 5.22.0

* Update changelog
2024-11-16 13:25:48 +01:00
a1934ee82b Feature/implement range slider in rule settings dialog (#4043)
* Implement range slider in rule settings dialog

* Update changelog
2024-11-16 12:17:58 +01:00
8c0de59414 Feature/move treemap chart from experimental to general availability (#4034)
* Move treemap chart to general availability

* Update changelog
2024-11-16 00:02:28 +01:00
95cb6dcb8d Feature/upgrade UUID to version 11.0.2 (#4029)
* Upgrade uuid to version 11.0.2

* Update changelog
2024-11-13 14:34:30 +01:00
92b025bff3 Feature/separate FIRE and X-ray pages (#4037)
* Separate FIRE / X-ray page

* Update changelog
2024-11-11 21:07:02 +01:00
15856264f8 Feature/upgrade ngx-skeleton-loader to version 9.0.0 (#4026)
* Upgrade ngx-skeleton-loader to version 9.0.0

* Update changelog
2024-11-11 19:26:43 +01:00
09a9148fec Bugfix/move changelog entry (#4038)
* Move changelog entry
2024-11-10 11:01:39 +01:00
6057794eb6 Feature/extend assistant by holding selector (#4031)
* Extend assistant by holding selector

* Update changelog
2024-11-10 10:29:43 +01:00
9f72835d58 Feature/improve language localization for it 20241109 (#4036)
* Update translations

* Update changelog
2024-11-09 20:20:13 +01:00
f800650c4d Release 2.122.0 (#4033) 2024-11-07 20:12:27 +01:00
70f2f01f8f Bugfix/fix algebraic sign in label of treemap chart (#4030)
* Fix algebraic sign

* Update changelog
2024-11-07 20:05:01 +01:00
8fb484af4d Bugfix/disable caching of benchmarks in markets overview if sharing (#4027)
* Disable caching of benchmarks if sharing mode

* Update changelog
2024-11-07 20:03:37 +01:00
190bb4b6fb Feature/refactor data gathering items (#4032)
* Refactoring
2024-11-07 19:57:37 +01:00
04d5416c6d Feature/harmonize date formats in import test files (#3910)
* Harmonize date formats
2024-11-06 18:09:44 +01:00
9c27fb70aa Feature/minor improvements in data provider service (#4017)
* Refactoring
2024-11-05 13:01:53 +01:00
316b7e82f1 Feature/upgrade countries-list to version 3.1.1 (#4020)
* Upgrade countries-list to version 3.1.1

* Update changelog
2024-11-04 19:05:01 +01:00
93001b68ad Feature/introduce lookup response interface (#4019)
* Introduce lookup response interface

* Refactor lookup item interface
2024-11-03 19:58:19 +01:00
a1fbdc2ebe Bugfix/exception handling in user authorization (#4015)
* Add guard

* Update changelog
2024-11-03 15:51:59 +01:00
c857bd1b2e Release 2.121.1 (#4014) 2024-11-02 22:29:28 +01:00
b2aa31f4ba Bugfix/handle missing Stripe api key exception (#4013)
* Conditionally initialize Stripe
2024-11-02 22:28:13 +01:00
de74d5c3d6 Release 2.121.0 (#4011) 2024-11-02 13:48:03 +01:00
5652d19a88 Feature/upgrade stripe dependencies 20241102 (#4009)
* Upgrade stripe dependencies

* Update changelog
2024-11-02 13:45:47 +01:00
a80ca507f8 Feature/add lastRequestAt to analytics (#4010)
* Add lastRequestAt to Analytics
2024-11-02 13:45:26 +01:00
5f56812125 Feature/extend promotion system (#4008)
* Extend promotion system
2024-11-02 13:44:57 +01:00
0004c5dabe Feature/revert permissions on entrypoint.sh in dockerfile (#4007)
* Revert permissions

* Update changelog
2024-11-02 13:36:33 +01:00
45c0487ba7 Bugfix/Set stack and container names in docker-compose files (#4005)
* Set stack and container names in docker-compose files
2024-10-31 17:34:18 +01:00
1ee9cd3de1 Feature/set stack and container names in docker-compose files (#4000)
* Set stack and container names in docker-compose files

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-31 13:37:56 +01:00
d7f69020c2 Release 2.120.0 (#4004) 2024-10-30 21:41:47 +01:00
db53ef54c4 Feature/use log levels to conditionally log prisma query events (#4003)
* Use LOG_LEVELS for prisma query events

* Update changelog
2024-10-30 21:39:51 +01:00
10e725b51a Feature/Add dataProviderGhostfolioDailyRequests to Analytics (#4001)
* Add dataProviderGhostfolioDailyRequests to Analytics
2024-10-30 21:39:20 +01:00
53ae0d8aa7 Feature/upgrade nx to version 20.0.6 (#3996)
* Upgrade Nx to version 20.0.6

* Update changelog
2024-10-29 18:47:54 +01:00
ab44773945 Feature/Switch consistent-indexed-object-style eslint rule from warn to off (#3999)
* Switch consistent-indexed-object-style eslint rule from warn to off

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-29 17:29:03 +01:00
5de176a7ef Feature/rename allocation cluster risk x ray rule (#3994)
* Rename Allocation Cluster Risk to Economic Market Cluster Risk

* Update changelog
2024-10-28 20:03:37 +01:00
6a195a7a36 Feature/extend sitemap.xml by resources sub pages in de (#3993)
* Extend sitemap.xml
2024-10-28 20:03:10 +01:00
e61d797d2d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-27 07:44:30 -07:00
0f19cb37e6 Feature/improve language localization for de 20241026 (#3991)
* Update translations

* Update changelog
2024-10-27 08:57:29 +01:00
c9c066c8f7 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-26 15:43:30 -07:00
c790d0df21 Feature/Restructure resources page (#3978)
* Restructure resources page

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-10-26 20:17:08 +02:00
8e2e73137c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-26 07:42:30 -07:00
e05f481344 Feature/Switch consistent-generic-constructors eslint rule from warn to error (#3985)
* Switch consistent-generic-constructors eslint rule from warn to error

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-26 12:20:40 +02:00
d4c5d58781 Feature/Switch prefer-optional-chain eslint rule from warn to error (#3983)
* Switch prefer-optional-chain eslint rule from warn to error

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-26 11:42:03 +02:00
6a10b932b0 Feature/Switch consistent-type-assertions eslint rule from warn to error (#3982)
* Switch consistent-type-assertions eslint rule from warn to error

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-26 11:07:48 +02:00
906e526cb9 Release 2.119.0 (#3990) 2024-10-26 10:31:24 +02:00
ed97029e1b Feature/upgrade prisma to version 5.21.1 (#3987)
* Upgrade prisma to version 5.21.1

* Update changelog
2024-10-26 10:29:24 +02:00
4104fb2f8f Feature/Improve subscription service (#3989)
* Various improvements
2024-10-26 10:29:04 +02:00
9a885e821e Feature/switch prefer-function-type eslint rule from warn to error (#3981)
* Switch prefer-function-type eslint rule from warn to error

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-26 09:58:43 +02:00
fccac5640d Bugfix/fix calculation of allocation cluster risk x ray rules (#3988)
* Fix calculation of allocation cluster risk X-ray rules

* Update changelog
2024-10-26 09:42:33 +02:00
405ec0d2b2 Feature/switch the consistent-type-definitions eslint rule from warn to error (#3980)
* Switch the consistent-type-definitions eslint rule from warn to error

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-26 09:41:37 +02:00
6bc4ffe03d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-25 11:41:30 -07:00
76ff34b0c4 Feature/switch no-empty-function rule in eslint configuration from warn to error (#3979)
* Switch no-empty-function rule in eslint configuration from warn to error

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-25 18:49:31 +02:00
0c2063343d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-24 19:40:31 -07:00
3baab79b54 Bugfix/fix x-axis of investment chart component to adapt on date range change (#3974)
* Fix x-axis of investment chart component to adapt on date range change

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-10-24 20:18:16 +02:00
3984d4363b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-23 19:39:30 -07:00
52164b4994 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-23 11:38:30 -07:00
a0445740c4 Release 2.118.0 (#3976) 2024-10-23 20:38:29 +02:00
e311ede3af Feature/Optimize pre-commit hook by linting only affected projects (#3973)
* Optimize pre-commit hook by linting only affected projects

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-23 20:36:54 +02:00
19b8c514f2 Feature/Add static portfolio analysis rule for developed Markets (#3975)
* Add static portfolio analysis rule: Allocation Cluster Risk (Developed Markets)

* Update changelog
2024-10-23 20:31:54 +02:00
e25aebf26f Feature/improve font color assignment in tree map chart component (#3954)
* Improve font color assignment in tree map chart component

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-10-23 14:32:17 +02:00
0718a2e3f9 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-22 19:37:30 -07:00
75b8f5828d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-22 11:37:30 -07:00
d325f8bfaf Feature/Add static portfolio analysis rule for emerging Markets (#3949)
* Add static portfolio analysis rule: Allocation Cluster Risk (Emerging Markets)

* Update changelog
2024-10-22 20:19:56 +02:00
2b3fcf4105 Feature/Quote redis password for healthcheck in docker-compose files (#3969)
* Quote redis password for healthcheck in docker-compose files

* Update changelog
2024-10-22 19:52:23 +02:00
51b68e561f Feature/add support for mutual funds in eod historical data service (#3970)
* Add support for mutual funds

* Update changelog
2024-10-22 15:25:38 +02:00
bdf9461148 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-21 19:36:01 -07:00
2951fb37f9 Feature/Upgrade nx to version 20.0.3 (#3968)
* Upgrade Nx to version 20.0.3

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-21 21:05:10 +02:00
277a1cb38d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-20 11:33:30 -07:00
a5331dd32b Feature/optimize dialog sizes for mobile (#3964)
* Optimize dialog sizes for mobile

* Update changelog
2024-10-20 17:59:44 +02:00
4c658a1f6a Bugfix/fix export was not found warning (#3963)
* Fix export was not found warning

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-20 17:23:29 +02:00
6d67d037eb Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-19 19:32:30 -07:00
6abb94ba33 Feature/improve language localization for de 20241019 (#3960)
* Update translations

* Update changelog
2024-10-19 21:04:07 +02:00
6ecd7ac768 Release 2.117.0 (#3962) 2024-10-19 21:02:47 +02:00
524adfc672 Feature/Clean up unused OnInit implementations (#3961)
* Clean up unused OnInit implementations
2024-10-19 20:59:42 +02:00
788c9d853c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-19 11:32:01 -07:00
308d3b64b1 Feature/Remove empty constructors (#3958)
* Remove empty constructors
2024-10-19 18:54:49 +02:00
a414cfab52 Feature/add data providers management to admin control panel (#3950)
* Add data providers management to admin control panel

* Update changelog
2024-10-19 18:19:33 +02:00
68cb4b27d1 Feature/add logotype to footer (#3947)
* Add logo type to footer

* Update changelog
2024-10-19 15:20:15 +02:00
6e4758a183 Feature/Improve backgrounds in tree map chart (#3938)
* Improve backgrounds in tree map chart

* Update changelog
2024-10-19 10:39:36 +02:00
6bb7c0d196 Bugfix/fix style selector in carousel component (#3948)
* Fix style selector

* Update changelog
2024-10-19 10:04:27 +02:00
d59dfc3d5b Feature/Archive discontinued personal finance tools (#3945)
* Archive discontinued personal finance tools
2024-10-19 10:04:10 +02:00
6e630bd5a5 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-17 19:30:01 -07:00
a9d26b319d Feature/Format database schema (#3943)
* Format
2024-10-17 20:42:59 +02:00
ba52e385a1 Feature/extend personal finance tools 20241017 (#3944)
* Add Leafs

* Add FIREkit
2024-10-17 20:42:24 +02:00
2903cb396e Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-17 11:29:30 -07:00
cfd5b6bbf2 Release 2.116.0 (#3942) 2024-10-17 17:46:03 +02:00
6e490e5710 Feature/extend self hosting faq by benchmarks for markets (#3940)
* Extend FAQ by benchmarks for Markets

* Update changelog
2024-10-17 14:15:52 +02:00
7f30424792 Feature/improve empty state in benchmarks of markets overview (#3939)
* Improve empty state

* Update changelog
2024-10-17 14:13:02 +02:00
021ef11daa Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-16 23:28:39 -07:00
d5fb32fb52 Feature/extend self hosting faq by benchmarks (#3937)
* Extend FAQ by benchmarks

* Update changelog
2024-10-16 21:19:38 +02:00
04b056e4e5 Feature/switch to adjusted market prices in get historical of eod historical data service (#3934)
* Switch to adjusted market prices in getHistorical()

* Update changelog
2024-10-16 20:51:18 +02:00
ce6f175f9a Bugfix/fix usage of processor portfolio snapshot computation concurrency env variable (#3936)
* Fix usage of PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY

* Update changelog
2024-10-16 20:48:27 +02:00
6696a4447a Feature/Refactor test ok-novn-buy-and-sell-partially to load json file (#3935)
* Refactor test to load json file
2024-10-16 20:44:45 +02:00
0403117e8c Feature/Set permission on entrypoint.sh in Dockerfile (#3903)
* Set permission on entrypoint.sh in Dockerfile

* Update changelog
2024-10-15 20:50:33 +02:00
144e5da753 Feature/add units to x ray rule settings (#3926)
* Introduce unit

* Update changelog
2024-10-15 20:49:04 +02:00
7f97168aa7 Feature/disable text hover effect in tree map chart (#3929)
* Disable text hover effect in tree map chart

* Update changelog
2024-10-15 20:10:35 +02:00
ab10b9da54 Feature/improve language localization for de 20241014 (#3927)
* Update translations

* Update changelog
2024-10-15 20:01:19 +02:00
6be8565442 Release 2.115.0 (#3925) 2024-10-14 21:41:50 +02:00
662bfb647e Feature/add sliders to customize x-ray rules (#3922)
* Add sliders to customize X-ray rules

* Update changelog
2024-10-14 21:38:57 +02:00
a14c10bad2 Feature/Enable unused compiler options in tsconfig (#3895)
* Enable noUnusedLocals noUnusedParameters in compiler options of tsconfig

* Update changelog
2024-10-14 10:49:18 +02:00
67a147bd96 Feature/Improve background color assignment in treemap chart component (#3918)
* Improve background color assignment in treemap chart component

* Update changelog
2024-10-13 16:13:26 +02:00
f8da265f5f Feature/restructure XRayRulesSettings (#3898)
* Restructure XRayRulesSettings

* Update changelog
2024-10-12 21:04:09 +02:00
aee5e833a5 Feature/harmonize processor concurrency environment variables (#3913)
* Harmonize processor concurrency environment variables

* Update changelog
2024-10-12 19:35:43 +02:00
3289bd5cc9 Feature/Simplify labels in treemap chart (#3912)
* Simplify labels in treemap chart

* Update changelog
2024-10-12 19:32:34 +02:00
4db5e68f5c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-12 07:32:53 -07:00
c44da0bce3 Feature/expose portfolio snapshot computation timeout as environment variable (#3894)
* Expose portfolio snapshot computation timeout as environment variable

* Update changelog
2024-10-12 09:04:58 +02:00
f9c6a0cef3 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-11 15:32:44 -07:00
7a11bb93d5 Feature/Set up unit test that loads activity from exported json file (#3901)
* Set up unit test that loads activity from exported json file

* Update changelog
2024-10-11 21:11:39 +02:00
d158d0c326 Feature/Extend tooltip in treemap chart component by name (#3907)
* Extend tooltip in treemap chart component by name

* Update changelog
2024-10-11 20:13:40 +02:00
5f4cbe3af7 Bugfix/considered language of user settings on login with Security Token (#3828)
* Consider language of user settings on login with Security Token

* Update changelog
2024-10-11 19:59:08 +02:00
69ab792b46 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-10 15:32:31 -07:00
f5c0d803a0 Release 2.114.0 (#3905) 2024-10-10 21:09:58 +02:00
7252a54ae0 Feature/Set up tooltip in treemap chart component (#3897)
* Set up tooltip in treemap chart component

* Update changelog
2024-10-10 21:06:17 +02:00
73cd2f9cb7 Bugfix/fix exception in portfolio details endpoint (#3896)
* Fix exception caused by markets and marketsAdvanced

* Update changelog
2024-10-10 20:27:33 +02:00
f3927b25a4 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-09 15:32:24 -07:00
98957d282e Feature/move tags from info to user service (#3859)
* Move tags from info to user service

* Update changelog
2024-10-09 21:04:37 +02:00
8db7996539 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-08 15:32:09 -07:00
fc5ed887ff Feature/Set prefer-const to error in eslint configuration (#3888)
* Set prefer-const to error in eslint configuration

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-08 15:30:56 +02:00
d93fec71b1 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-07 15:32:00 -07:00
7e339972ad Feature/extend public api with health check endpoint (#3825)
* Add health check endpoint documentation

* Update changelog
2024-10-07 17:49:29 +02:00
2725b94169 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-06 23:31:49 -07:00
14b88edff6 Feature/extend MSFT buy with dividend test (#3891)
* Extend test
2024-10-06 21:07:07 +02:00
29d3b762a8 Feature/switch typescript-eslint no-unused-vars to error (#3887)
* Switch @typescript-eslint/no-unused-vars to error
2024-10-06 18:49:42 +02:00
adf9a71cbd Release 2.113.0 (#3886) 2024-10-06 17:08:10 +02:00
bce18f7261 Feature/reuse advanced markets calculation in portfolio details endpoint (#3884) 2024-10-06 17:05:06 +02:00
f0f67cdacb Feature/set up stylisticTypeChecked rule in eslint configuration (#3878)
* Set up stylisticTypeChecked rule in eslint configuration

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-06 16:57:56 +02:00
f48ce4e1ae Feature/reuse markets calculation in portfolio details endpoint (#3883)
* Reuse markets calculation in portfolio details endpoint

* Update changelog
2024-10-06 11:03:11 +02:00
b50a1fc63d Feature/reuse markets calculation in public portfolio endpoint (#3882)
* Reuse markets calculation in public portfolio endpoint

* Update changelog
2024-10-06 10:27:42 +02:00
8cbefbc1f4 Feature/move prisma to devDependencies (#3880) 2024-10-05 21:11:51 +02:00
b0c2d3cddf Feature/add lint step to build-code.yml workflow (#3879)
* add lint step to build-code.yml workflow

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-05 20:53:33 +02:00
dc3f25583a Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-05 11:31:35 -07:00
ecd75b5d8a Feature/optimize portfolio snapshot computation by reusing date intervals (#3855)
* Optimize portfolio snapshot computation by reusing date intervals

* Update changelog
2024-10-05 18:08:59 +02:00
e715ce14e5 Feature/switch typescript-eslint no-unused-expressions rule to error (#3872)
* Switch @typescript-eslint/no-unused-expressions to error (remove from eslint configuration)
2024-10-05 17:52:01 +02:00
e2ae43bf28 Feature/set up recommendedTypeChecked rule in eslint configuration (#3876)
* Set up recommendedTypeChecked rule in eslint configuration

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-05 17:42:07 +02:00
9a2ca9cd08 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-05 03:31:33 -07:00
fd26253e7a Feature/set up husky and pre-commit hook for linting and format check (#3867)
* Set up husky and pre-commit hook for linting and format check

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-05 10:45:39 +02:00
f3e2091ff4 Feature/refactor markets calculation in details of portfolio service (#3871)
* Refactor markets calculation
2024-10-05 09:58:09 +02:00
a597a58dad Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-04 19:31:30 -07:00
bcc8cb1e5b Feature/modernize rules implementation (#3869)
* Modernize rules implementation

* Include markets in details
2024-10-04 21:44:07 +02:00
252fb3fe11 Bugfix/Handle exception in historical market data gathering of derived currencies (#3858)
* Handle exception in historical market data gathering of derived currencies

* Update changelog

---------

Co-authored-by: Dan <dan@ddev.ch>
2024-10-04 19:49:03 +02:00
6fa0400980 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-03 19:31:19 -07:00
c4742d3a53 Release 2.112.0 (#3864) 2024-10-03 20:43:04 +02:00
dd28f38e60 Bugfix/fix eslint configuration (#3852)
* Fix eslint configuration

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-10-03 20:39:56 +02:00
9b8f552ee2 Feature/improve language localization for de 20241003 (#3863)
* Update translations
2024-10-03 20:35:34 +02:00
413076141c Bugfix/fix calculation of allocations by market (#3853)
* Fix calculation of allocations by market (unknown)

* Update changelog
2024-10-03 20:18:26 +02:00
85a7838a96 Feature/show message if no results have been found in activity dialog (#3854)
* Show message if no results have been found in activity dialog

* Update changelog

---------

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-10-03 20:14:06 +02:00
3e418b10fd Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-02 19:31:09 -07:00
8364f7f703 Feature/set up output for click in activities table component (#3856)
* Set up @output for click in activities table component
2024-10-02 21:15:10 +02:00
84457f4816 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-02 11:31:06 -07:00
7f6b8145d7 Feature/set up output for click in holdings table component (#3851)
* Set up @Output for click in holdings table component
2024-10-02 13:27:55 +02:00
2ad91e5796 Feature/optimize portfolio calculations with smarter date interval selection (#3829)
* Optimize portfolio calculations with smarter date interval selection

* Update changelog
2024-10-02 11:59:57 +02:00
a36404d375 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-01 19:31:03 -07:00
24a3d92da0 Feature/add business logic of rule settings (#3826)
* Add business logic of rule settings

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-10-01 19:13:45 +02:00
1b2a7dc2e4 Feature/improve language localization for de 20240929 (#3844)
* Update translations

* Update changelog
2024-10-01 18:39:42 +02:00
a6d68ae296 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-30 19:30:53 -07:00
feb3de768a Feature/add unit test for redact attributes of object helper (#3846)
* Add unit test for redactAttributes()
2024-09-30 18:52:37 +02:00
5deeb8861e Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-29 16:25:20 -07:00
d00d7ac1dd Feature/add support for archived product page (#3843)
* Add support for archived product page
2024-09-29 19:27:02 +02:00
04e0bbd4c1 Feature/add tooltips to product page (#3842)
* Add tooltips
2024-09-29 19:26:30 +02:00
0b596b6541 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-28 16:25:12 -07:00
a05203c785 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-28 12:25:12 -07:00
00f06d6aef Release 2.111.0 (#3838) 2024-09-28 20:44:21 +02:00
85cc72627b Feature/add user id to tag database schema (#3837)
* Add user id to tag database schema

* Update changelog
2024-09-28 20:42:25 +02:00
e8f0d2bb14 Feature/consider availability of date range selector and filters in assistant per view (#3824)
* Consider availability of date range selector and filters in assistant per view

* Update changelog
2024-09-28 18:25:06 +02:00
33de8a10bb Feature/optimize portfolio calculations with smarter cloning of activities (#3827)
* Optimize portfolio calculations with smarter cloning of activities

* Update changelog
2024-09-28 10:23:35 +02:00
ed30549f6b Feature/prepare permission to delete asset profile of currency (#3833) 2024-09-28 09:47:48 +02:00
7d8f34a90f Feature/upgrade prisma to version 5.20.0 (#3817)
* Upgrade prisma to version 5.20.0

* Update changelog
2024-09-28 09:45:22 +02:00
8d0890a400 Feature/integrate add currency to create asset profile dialog (#3819)
* Integrate add currency to create asset profile dialog

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-09-28 09:42:14 +02:00
64c1d25e64 Feature/improve language localization for de 20240926 (#3823)
* Update translations

* Update changelog
2024-09-27 20:15:47 +02:00
395d7f08ac Feature/upgrade webpack bundle analyzer to version 4.10.2 (#3818)
* Upgrade webpack-bundle-analyzer to version 4.10.2

* Update changelog
2024-09-25 18:07:24 +02:00
ba438c25ef Release 2.110.0 (#3816) 2024-09-24 19:47:46 +02:00
bb445ddf2e Feature/improve experimental chart in account detail dialog (#3813)
* Improve chart in account detail dialog

* Update changelog
2024-09-24 19:45:48 +02:00
4a97e2bb54 Feature/align holdings and regions of public page with allocations page (#3815)
* Align holdings and regions of public page with allocations page

* Update changelog
2024-09-24 19:17:58 +02:00
e301dc5612 Feature/add horizontal lines to separate delete actions in menus (#3805)
* Add horizontal lines to separate delete action

* Update changelog
2024-09-24 18:53:25 +02:00
e7f10ad4ad Bugfix/fix typo and update date of Hacktoberfest 2024 blog post (#3811)
* Fix typo and update date
2024-09-24 11:10:59 +02:00
1ed71ed7ec Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-24 00:15:23 -07:00
46fb075ecb Feature/improve language localization for de 20240921 (#3804)
* Update translations

* Update changelog
2024-09-23 19:49:00 +02:00
98e9b5d895 Feature/consider user language in sharable portfolio link (#3806)
* Consider user’s language in sharable portfolio link

* Update changelog
2024-09-22 09:09:02 +02:00
67aa76d31c Release 2.109.0 (#3803) 2024-09-21 20:03:31 +02:00
f59e8c8798 Feature/add feature graphic for hacktoberfest 2024 blog post (#3800)
* Add feature graphic
2024-09-21 20:01:33 +02:00
336f7b002c Feature/extend personal finance tools (#3801)
* Add Finanzfluss Copilot
2024-09-21 20:00:56 +02:00
be09acdb24 Feature/expose concurrency of data gathering processor as environment variable (#3799)
* Expose concurrency of data gathering processor

* PROCESSOR_CONCURRENCY_GATHER_ASSET_PROFILE
* PROCESSOR_CONCURRENCY_GATHER_HISTORICAL_MARKET_DATA

* Update changelog
2024-09-21 18:56:02 +02:00
6f227e677c Feature/refactor environment variable access in create user of user service (#3797)
* Refactor environment variable access
2024-09-21 18:22:14 +02:00
e918970feb Feature/expose concurrency of portfolio snapshot calculation as environment variable (#3796)
* Expose PROCESSOR_CONCURRENCY_PORTFOLIO_SNAPSHOT

* Update changelog
2024-09-21 18:16:34 +02:00
5e4201d831 Feature/remove nx cloud access token (#3798)
* Remove nxCloudAccessToken
2024-09-21 15:04:52 +02:00
20d709380a Feature/add no auto-renewal hint to account membership page (#3795)
* Add hint
2024-09-21 14:34:17 +02:00
7053aba2da Feature/add portfolio performance metrics to public page (#3793)
* Add portfolio performance metrics

* Update changelog
2024-09-21 13:28:49 +02:00
c2f69501c7 Feature/improve documentation for public API (#3792) 2024-09-21 11:34:26 +02:00
583c14128b Feature/extend public api with portfolio performance metrics endpoint (#3762)
* Extend Public API with portfolio performance metrics endpoint

* Update changelog
2024-09-21 10:42:43 +02:00
9059d4f971 Feature/upgrade prisma to version 5.19.1 (#3784)
* Upgrade prisma to version 5.19.1

* Update changelog
2024-09-21 10:36:05 +02:00
9b07b19523 Feature/improve usability of create or update access dialog (#3791)
* Improve usability

* Dialog height
* Always show permission selector

* Update changelog
2024-09-21 10:35:41 +02:00
7761e4d712 Feature/add hacktoberfest 2024 blog post (#3790)
* Add blog post: Hacktoberfest 2024

* Update changelog
2024-09-21 10:05:52 +02:00
3cd77523a1 Feature/improve loading indicator of accounts table (#3761)
* Improve loading indicator

* Update changelog
2024-09-20 11:10:19 +02:00
2bd14b135c Feature/extract locales 20240919 (#3785) 2024-09-19 21:30:49 +02:00
f0df8a5254 Feature/add snake-case hint to localized routes (#3783)
* Add snake-case hint to localized routes
2024-09-19 20:54:00 +02:00
cb472c0884 Feature/improve ghostfolio po polsku (#3782)
* Improve Ghostfolio po polsku
2024-09-19 20:28:09 +02:00
d1f6601c5e Feature/Improve language localization for pl (#3780)
* Update translations

* Update changelog
2024-09-19 20:11:27 +02:00
97467a3809 Feature/improve language localization for de 20240917 (#3778)
* Update translations

* Update changelog
2024-09-18 15:58:19 +02:00
22127b5915 Release 2.108.0 (#3777) 2024-09-17 20:17:23 +02:00
4865aa1665 Feature/add fallback in get quotes of eod historical data service (#3776)
* Add fallback to previousClose in getQuotes()

* Update changelog
2024-09-17 20:15:16 +02:00
520c176cd6 Feature/add copy link to clipboard action to access table (#3768)
* Add copy link to clipboard action

* Update changelog
2024-09-17 20:07:27 +02:00
df9a0ec35a Feature/support bonds in import dividend dialog (#3775)
* Support bonds

* Update changelog
2024-09-17 19:36:48 +02:00
d703088611 Feature/improve ux of toggle component (#3769)
* Improve usability via cursor styles

* Update changelog
2024-09-16 15:06:02 +02:00
38ac3d387b Feature/extend market data endpoint by lastMarketPrice (#3752)
* Extend market data endpoint by lastMarketPrice

* Integrate last market price in admin market data

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-09-15 20:18:24 +02:00
1b90fba656 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-14 14:58:43 -07:00
fbf377f67f Feature/set up rule settings dialog (#3771) 2024-09-14 20:44:36 +02:00
3de192c65e Feature/expose thresholds in rule settings (#3770) 2024-09-14 19:42:37 +02:00
9fb80e5067 Feature/remove accounts from holding endpoint (#3765)
* Clean up accounts
2024-09-14 17:29:52 +02:00
d236ecfe85 Feature/extend personal finance tools 20240914 (#3767)
* Add Buxfer

* Add Moneydance

* Add Banktivity

* Add Microsoft Money

* Add Masttro

* Add WealthPosition
2024-09-14 17:29:01 +02:00
323cfbfcaa Feature/introduce filters in account endpoint (#3764)
* Introduce filters in acount endpoint

* Integrate endpoint in holding detail dialog

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-09-14 10:53:39 +02:00
8735fc3fad Bugfix/fix typo in changelog (#3758) 2024-09-14 10:27:12 +02:00
3eba400d21 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-12 19:05:35 -07:00
7b8dc480f4 Release 2.107.1 (#3757) 2024-09-12 20:35:42 +02:00
5a4f1c03cb Feature/extend personal finance tools (#3751)
Add etops
2024-09-12 20:34:06 +02:00
0ef2b82852 Bugfix/fix destructuring in activities filters (#3756)
* Provide default value during destructuring

* Update changelog
2024-09-12 20:33:21 +02:00
c4cbdfc643 Release 2.107.0 (#3748) 2024-09-10 20:34:36 +02:00
403ee2741d Feature/set up portfolio snapshot queue (#3725)
* Set up portfolio snapshot queue

* Update changelog
2024-09-10 20:32:08 +02:00
383a02519a Feature/extend filters of order endpoint (#3743)
* Extend filters of order endpoint

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-09-10 20:22:51 +02:00
a5211f6a29 Feature/upgrade bull to version 4.16.2 (#3746)
* Upgrade bull to version 4.16.2

* Update changelog
2024-09-10 20:01:37 +02:00
9edffd100e Feature/improve language localization for fr (#3724)
* Update translations

* Update changelog
2024-09-10 17:36:24 +02:00
6db881b08f Feature/optimize admin control panel endpoint using promise.all (#3741)
* Optimize by using Promise.all()

* Update changelog
2024-09-10 14:39:52 +02:00
557a0bf808 Feature/optimize info endpoint using promise.all (#3742)
* Optimize by using Promise.all()

* Update changelog
2024-09-09 13:20:55 +02:00
bdb3a8f1dc Feature/improve language localization for Italian (#3744)
* Update translations

* Update changelog
2024-09-08 21:41:41 +02:00
9cd4321bd0 Feature/extract users from admin control panel endpoint to dedicated endpoint (#3740)
* Introduce GET api/v1/admin/user endpoint

* Update changelog
2024-09-08 09:56:08 +02:00
728f84e7eb Release 2.106.0 (#3738) 2024-09-07 21:24:33 +02:00
1bc2b47452 Feature/setup skeleton loader for data tables (#3735)
* Setup skeleton loader for data tables

* Update changelog
2024-09-07 21:22:56 +02:00
8c322b4e81 Bugfix/fix historical market data gathering in yahoo finance service (#3737)
* Switch from historical() to chart()

* Update changelog
2024-09-07 21:21:02 +02:00
1204240ed0 Bugfix/fix exception in admin market data detail component (#3731)
* Add check for dateOfFirstActivity

* Update changelog
2024-09-06 10:29:53 +02:00
df5e2f5f0e Feature/extract common CACHE_TTL as constants (#3722)
Extract CACHE_TTL

* CACHE_TTL_NO_CACHE
* CACHE_TTL_INFINITE
2024-09-05 18:23:38 +02:00
fb44933c9c Feature/improve error logs in scraper configuration test (#3730)
* Improve error logs

* Update changelog
2024-09-05 18:21:32 +02:00
7ea9061852 Feature/execute scraper configuration instantly (#3723)
* Execute scraper configuration instantly

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-09-04 21:17:38 +02:00
8018236942 Bugfix/fix carousel component (#3709)
* Fix carousel component

* Update changelog
2024-09-04 20:19:59 +02:00
d6dbc0d9e3 Release 2.106.0-beta.6 (#3728) 2024-09-03 20:44:42 +02:00
c48e4ec4c6 Feature/improve usage of portfolio calculator in holding endpoint (#3727)
* Improve usage of portfolio calculator
2024-09-03 20:42:49 +02:00
a6d9f5dd69 Bugfix/load data once on portfolio activities page (#3726)
* Fetch activities only once

* Update changelog
2024-09-03 19:24:14 +02:00
8fab73f122 Feature/update OSS friends (#3718) 2024-09-03 14:12:16 +02:00
3fa32b2ae9 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-31 09:46:44 -07:00
676be076c3 Release 2.106.0-beta.5 (#3721) 2024-08-31 16:50:25 +02:00
adcddae44e Encode redis password (#3720) 2024-08-31 16:49:05 +02:00
5358f13e93 Release 2.106.0-beta.4 (#3719) 2024-08-31 16:13:56 +02:00
71a568264d Feature/improve portfolio snapshot caching (#3717)
* Improve portfolio snapshot caching

* Update changelog
2024-08-31 16:11:52 +02:00
c6f804f68c Feature/Improve redis cache part 2 (#3716)
* Set ttl to 0
2024-08-31 16:11:18 +02:00
e4ba27850d Feature/upgrade prisma to version 5.19.0 (#3715)
* Upgrade prisma to version 5.19.0

* Update changelog
2024-08-31 14:29:45 +02:00
0fc83486dc Feature/improve redis cache (#3714)
* Improve redis cache

* Update changelog
2024-08-31 10:57:26 +02:00
267023f2c9 Feature/improve language localization for polish (#3713)
* Improve language localization for Polish
2024-08-31 10:46:24 +02:00
68114d99be Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-30 09:46:32 -07:00
b794c4dcc8 Feature/add todo for emergency fund positions value calculation (#3684)
* Add todo
2024-08-30 11:54:23 +02:00
2c54e566ed Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-29 17:46:28 -07:00
7a695f34f2 Feature/add docker pulls shield to README.md (#3705)
Add Docker Pulls shield
2024-08-29 16:13:46 +02:00
72f11d8f13 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-28 17:46:16 -07:00
208e5c8adb Release 2.106.0-beta.3 (#3706) 2024-08-28 20:29:59 +02:00
c4a28c6bff Feature/set up a performance logging service (#3703)
* Setup performance logging service

* Update changelog
2024-08-28 20:28:36 +02:00
4505441691 Feature/improve language localization for polish (#3691)
* Improve language localization for Polish

* Update changelog
2024-08-28 20:28:11 +02:00
da38abff62 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-27 17:46:04 -07:00
bc51f106e9 Feature/expose log levels as env variable (#3704)
* Expose log levels as env variable

* Update documentation

* Update changelog
2024-08-27 20:30:08 +02:00
7d80d1ad3c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-26 13:45:49 -07:00
80330782d2 Release 2.106.0-beta.2 (#3702) 2024-08-26 16:23:34 +02:00
d08e8b4fd8 Feature/expose max chart items as env variable (#3701)
* Expose MAX_CHART_ITEMS as env variable

* Update changelog
2024-08-26 16:21:16 +02:00
633995c9c8 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-25 12:51:55 -07:00
4a8142b326 Release 2.106.0-beta.1 (#3699) 2024-08-25 10:15:01 +02:00
7db7eeecf2 Bugfix/fix view mode toggle of holdings tab (#3698)
* Fix view mode toggle

* Update changelog
2024-08-25 10:13:03 +02:00
e4074f95c9 Feature/handle activities of excluded accounts (#3697)
* Handle activities of excluded accounts
2024-08-25 10:03:40 +02:00
e23019a115 Feature/improve liabilities in portfolio calculator (#3696)
Improve liabilities
2024-08-25 09:03:39 +02:00
a8e0bb5a21 Bugfix/fix division by zero in performance calculation (#3695)
* Fix division by zero

* Update changelog
2024-08-25 09:02:14 +02:00
b334ab351b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-24 15:36:16 -07:00
c28af12cbd Release 2.106.0-alpha.1 (#3694) 2024-08-24 17:03:20 +02:00
f360a12823 Feature/rework portfolio calculator (#3393)
* Rework portfolio calculation

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-08-24 16:59:50 +02:00
4984d79bc7 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-23 15:36:11 -07:00
84d23764db Feature/minor improvements in client (#3690)
* Improve view mode form control value change

* Clean up
2024-08-23 20:26:17 +02:00
1aed45769d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-22 15:36:02 -07:00
d034eb04c4 Feature/improve development instructions (#3683) 2024-08-22 19:17:28 +02:00
099896f46b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-21 11:35:50 -07:00
724188847a Release 2.105.0 (#3688) 2024-08-21 19:44:57 +02:00
ea1b6fdf6e Feature/improve language localization for de 20240821 (#3687)
* Update translations

* Update changelog
2024-08-21 19:43:27 +02:00
2b212078b8 Feature/add support to deactivate x-ray rules (#3537)
* Add support to deactivate X-ray rules

* Update changelog
2024-08-21 19:38:37 +02:00
ac5aec9262 Feature/extend personal finance tools 20240820 (#3685)
* Add Ziggma
2024-08-21 19:36:45 +02:00
32f3c3a1f2 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-20 07:35:42 -07:00
f883469b55 Feature/move development instructions to DEVELOPMENT.md (#3680)
* Move development instructions
2024-08-20 11:43:17 +02:00
9f5707d4f5 Feature/extend personal finance tools 20240819 (#3682)
* Add CoinTracking
2024-08-20 11:42:44 +02:00
dc3a21936b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-18 07:35:20 -07:00
12c722afe1 Bugfix/Use currency conversion for fees and values (#3672)
* Use currency conversion for fees and values

* Update changelog
2024-08-18 10:02:21 +02:00
6d931134dd Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-17 23:35:17 -07:00
2de959f080 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-17 19:35:17 -07:00
6c79ccb2a9 Release 2.104.1 (#3679) 2024-08-17 22:34:56 +02:00
bce9b2f4bb Bugfix/fix clone functionality of activity (#3678)
* Fix clone functionality

* Update changelog
2024-08-17 22:32:23 +02:00
25ea451eb6 Release 2.104.0 (#3676) 2024-08-17 13:01:23 -07:00
d99b1a4cec Feature/reuse notification service for confirm dialogs (#3671)
* Reuse notification service for confirm dialogs
2024-08-17 13:01:23 -07:00
bb892449ca Bugfix/fix parse date in date helper (#3675)
Handle empty date string
2024-08-17 13:01:23 -07:00
36e987d0f1 Feature/upgrade types of color to version 3.0.6 (#3668)
* Upgrade @types/color to version 3.0.6
2024-08-17 13:01:23 -07:00
ac1ac67b21 Feature/upgrade date-fns to version 3.6.0 (#3673)
* Upgrade date-fns to version 3.6.0

* Update changelog
2024-08-17 13:01:23 -07:00
581651ef1d Feature/reuse notification service for alert dialogs (#3670)
* Reuse notification service for alert dialogs
2024-08-17 13:01:23 -07:00
8bd3e6c79b Feature/upgrade types of lodash to version 4.17.7 (#3667)
Upgrade @types/lodash to version 4.17.7
2024-08-17 13:01:23 -07:00
7b2d8e4d3a Release 2.104.0 (#3676) 2024-08-17 17:05:42 +02:00
952c2b71a2 Feature/reuse notification service for confirm dialogs (#3671)
* Reuse notification service for confirm dialogs
2024-08-17 16:55:49 +02:00
26277803c6 Bugfix/fix parse date in date helper (#3675)
Handle empty date string
2024-08-17 16:21:13 +02:00
130c6da6fc Feature/upgrade types of color to version 3.0.6 (#3668)
* Upgrade @types/color to version 3.0.6
2024-08-16 22:08:35 +02:00
75dd43637c Feature/upgrade date-fns to version 3.6.0 (#3673)
* Upgrade date-fns to version 3.6.0

* Update changelog
2024-08-15 19:30:03 +02:00
56ddbaf972 Feature/reuse notification service for alert dialogs (#3670)
* Reuse notification service for alert dialogs
2024-08-15 11:36:55 +02:00
8945f5c784 Feature/upgrade types of lodash to version 4.17.7 (#3667)
Upgrade @types/lodash to version 4.17.7
2024-08-14 11:29:12 +02:00
efed1d3e1a Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-13 22:27:04 -07:00
18df8cebeb Feature/upgrade zone.js to version 0.14.10 (#3669)
* Upgrade zone.js to version 0.14.10

* Update changelog
2024-08-13 15:31:17 +02:00
706784f7a0 Feature/set up notification service (#3663)
* Set up notification service

* Update changelog
2024-08-13 00:22:35 -07:00
716f979502 Feature/refactor dark theme css selector (#3662)
* Refactor dark theme CSS selector

* Update changelog
2024-08-13 00:22:35 -07:00
e42d2b1702 Bugfix/remove read_only true from docker-compose.yml (#3653)
* Removed read_only: true

* Update changelog
2024-08-13 00:22:35 -07:00
001091a413 Feature/improve language localization for de 20240810 (#3660)
* Update translations

* Update changelog
2024-08-13 00:22:35 -07:00
2893d71377 Feature/set up notification service (#3663)
* Set up notification service

* Update changelog
2024-08-11 18:33:33 +02:00
9246a73f41 Feature/refactor dark theme css selector (#3662)
* Refactor dark theme CSS selector

* Update changelog
2024-08-11 15:43:02 +02:00
d1276dc1a7 Bugfix/remove read_only true from docker-compose.yml (#3653)
* Removed read_only: true

* Update changelog
2024-08-11 14:31:04 +02:00
475bc3b86d Feature/improve language localization for de 20240810 (#3660)
* Update translations

* Update changelog
2024-08-11 11:09:21 +02:00
ed85520143 update ignore 2024-08-10 00:33:47 -07:00
8c536b45b9 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-10 00:33:23 -07:00
46553da2c3 Release 2.103.0 (#3658) 2024-08-10 09:02:49 +02:00
c34959896c Feature/improve color assignment with annualized performance in treemap chart (#3657)
* Improve color assignment

* Update changelog
2024-08-10 09:01:28 +02:00
2bbad8f4b0 Feature/add logging to benchmark service (#3654)
* Add logging
2024-08-10 07:58:16 +02:00
49485f79d2 Feature/enable experimental languages in user account settings (#3655)
* Enable ca and pl as experimental

* Update changelog
2024-08-10 07:57:45 +02:00
0f46ca4db2 Feature/extend personal finance tools 20240809 (#3656)
* Add Sarmaaya.pk

* Add Investify

* Add FINATEKA

* Add Plainzer
2024-08-10 07:57:19 +02:00
8bb3c809eb Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-09 15:59:10 -07:00
9f5da976f2 Feature/improve language localization for portuguese (#3651)
* Update translations

* Update changelog
2024-08-09 19:38:03 +02:00
b8d9bafef6 Feature/upgrade prisma to version 5.18.0 (#3646)
* Upgrade prisma to version 5.18.0

* Update changelog
2024-08-09 19:36:53 +02:00
1fd0c08477 update files 2024-08-09 02:19:48 -07:00
ae1b0a5bd2 fix index 2024-08-09 02:19:29 -07:00
4504462352 Merge pull request 'Fix pagination' (#2) from fix-pagination into main
Reviewed-on: #2
2024-08-09 02:16:55 -07:00
99558fec44 Fix pagination
Fix items on pages after the 1st resetting index to 1
2024-08-09 02:15:24 -07:00
7462ccd612 Merge pull request 'Add expand/collapse for Top/Bottom secion off portfolio analysis page' (#1) from add-expand-topbottom into main
Reviewed-on: #1
2024-08-08 19:38:58 -07:00
bd1233b0e2 Add expand/collapse for Top/Bottom secion off portfolio analysis page
- Add button to expand/collapse section
    - Show all positions when expanded
- Add pagination with 10 items per page
2024-08-08 19:35:20 -07:00
40de0cead5 Feature/optimize docker image (#3642)
* Remove redundant docker image layers

* Update prisma binary target of prisma

* Update changelog
2024-08-08 17:42:05 +02:00
43f5bb7773 Release 2.102.0 (#3648) 2024-08-07 20:46:57 +02:00
e85cc0fcfc Feature/clone or edit activity from account detail dialog (#3647)
* Clone or edit activity from holding detail dialog

* Update changelog
2024-08-07 20:45:46 +02:00
dc1948016f Feature/clone or edit activity from holding detail dialog (#3644)
* Clone or edit activity from holding detail dialog

* Update changelog
2024-08-07 20:45:03 +02:00
4410040a14 Feature/update angular url in README.md (#3566)
Update Angular url
2024-08-06 16:53:31 +02:00
b2ed0b2c80 Feature/improve caching of benchmarks in markets overview (#3640)
* Improve caching

* Update changelog
2024-08-05 19:44:24 +02:00
42fe653e1e Bugfix/fix cache flush endpoint response (#3641)
* Fix cache flush endpoint response

* Update changelog
2024-08-05 19:43:25 +02:00
8a81fa814f Feature/improve language localization for pl (#3643)
* Update translations
2024-08-04 16:52:32 +02:00
98f3fa9d7c Feature/improve language localization for de 20240804 (#3639)
* Update translations

* Update changelog
2024-08-04 09:24:48 +02:00
202e27fe25 Feature/improve language localization for polish (#3637)
* Improve language localization for Polish

* Update changelog
2024-08-04 08:56:55 +02:00
757ff527d0 Feature/extend personal finance tools 20240803 (#3634)
* Add Capitalyse
2024-08-04 08:35:01 +02:00
41f5801b5e Feature/refactor unique asset type to asset profile identifier (#3636)
* Refactoring
2024-08-04 08:27:05 +02:00
4c7657a90e Feature/upgrade nx to version 19.5.6 (#3633)
* Upgrade Nx to version 19.5.6

* Update changelog
2024-08-04 08:22:32 +02:00
aef650753e Feature/clean up activities page (#3635)
* Clean up
2024-08-04 08:18:54 +02:00
420f331be9 Release 2.101.0 (#3632) 2024-08-03 16:58:08 +02:00
e0068c4d5d Feature/harden container security following OWASP best practices (#3614)
* Harden container security

* Update changelog
2024-08-03 16:55:18 +02:00
85661884a6 Release 2.100.0 (#3631) 2024-08-03 15:47:52 +02:00
8f6203d296 Feature/manage tags of holdings (#3630)
* Manage tags of holdings

* Update changelog
2024-08-03 15:46:01 +02:00
2fa723dc3c Bugfix/fix language selector of user account settings (#3613)
Fix value of Català
2024-08-03 15:42:21 +02:00
a500fb72c5 Feature/refactor Angular Material theme (#3629) 2024-08-02 20:57:03 +02:00
02db0db733 Feature/persist view mode of holdings tab on home page (#3624)
* Persist view mode of holdings in user settings

* Update changelog
2024-08-02 20:27:58 +02:00
c87b08ca8b Feature/improve language localization for es (#3625)
* Update translations

* Update changelog
2024-08-01 20:28:42 +02:00
fcc2ab1a48 Feature/change color assignment by annualized performance in treemap chart (#3617)
* Change color assignment to annualized performance

* Update changelog
2024-07-31 19:18:50 +02:00
7efda2f890 Feature/improve language localization for Catalan (#3598)
* Update translations

* Update changelog
2024-07-30 14:30:08 +02:00
3794a61d2d Release 2.99.0 (#3618) 2024-07-29 20:10:26 +02:00
c1d1ea9dde Feature/migrate from Yarn 1 (Classic) to npm (#3601)
* Migrate from yarn to npm
2024-07-29 20:08:43 +02:00
0d676a46c8 Release 2.98.0 (#3615) 2024-07-27 19:53:44 +02:00
97db144e01 Feature/skip derived currencies in get quotes of data provider service (#3610)
* Skip derived currencies

* Update changelog
2024-07-27 19:47:06 +02:00
cec55127c8 Bugix/fix dividend import from data provider for holdings without account (#3606)
* Fix dividend import for holdings without account

* Update changelog
2024-07-27 19:45:12 +02:00
f3f359bcfb Feature/Improve language localization for spanish (#3612)
* Update messages.es.xlf
2024-07-26 15:55:26 +02:00
601e6f4147 Feature/improve account selector of create or update activity dialog (#3607)
* Improve empty value of account selector

* Update changelog
2024-07-25 19:39:07 +02:00
e228b4925c Feature/update notes of personal finance tools (#3611)
* Update notes
2024-07-25 19:38:52 +02:00
62e3ffe413 Feature/upgrade prisma to version 5.17.0 (#3597)
* Upgrade prisma to version 5.17.0

* Update changelog
2024-07-24 19:28:05 +02:00
6af885fde0 Feature/improve language localization for Spanish (#3605)
* Improve language localization for Spanish

* Update changelog
2024-07-24 11:51:58 +02:00
dd15bba359 Bugfix/fix public page for non existent access (#3604)
* Handle non-existent access

* Update changelog
2024-07-23 21:00:20 +02:00
43fca7ff43 Feature/improve personal finance tools product page (#3599)
* Localize origin
* Localize regions
* Localize tags
2024-07-23 20:59:23 +02:00
faa6af5694 Feature/improve handling of numerical precision in value component (#3595)
* Improve handling of numerical precision in value component

* Update changelog
2024-07-22 19:35:25 +02:00
d2ea7a0bfb Feature/upgrade nx to version 19.5.1 (#3596)
* Upgrade angular and Nx

* Update changelog
2024-07-21 09:41:16 +02:00
3f6319e00b Feature/setup catala (#3593)
* Set up Català

* Update changelog
2024-07-20 17:13:44 +02:00
5601299648 Release 2.97.0 (#3592) 2024-07-20 11:24:47 +02:00
6060c7cfe0 Feature/upgrade prettier to version 3.3.3 (#3586)
* Upgrade prettier to version 3.3.3

* Update changelog
2024-07-20 11:18:00 +02:00
ba78c2783d Feature/improve numerical precision in holding detail dialog (#3584)
* Improve numerical precision in holding detail dialog

* Update changelog
2024-07-20 11:17:36 +02:00
48eee5f865 Feature/upgrade node.js from version 18 to 20 (#3553)
* Upgrade to Node.js 20

* Update changelog
2024-07-20 10:30:05 +02:00
f4a8acdb46 Feature/add selfh.st logo to landing page (#3582)
* Add selfh.st

* Update changelog
2024-07-20 10:13:28 +02:00
1d6ba22598 Feature/improve language localization for de (#3583)
* Update translations
2024-07-20 10:12:49 +02:00
e38be8d710 Feature/upgrade nx to version 19.4.3 (#3581)
* Upgrade Nx to version 19.4.3

* Update changelog
2024-07-19 11:56:18 +02:00
da5be3fb57 Feature/reuse open-color in portfolio proportion chart component (#3562)
* Reuse open-color
2024-07-18 10:14:12 +02:00
b5317a7f95 Feature/improve language localization for de 20240715 (#3574)
* Update translations

* Update changelog
2024-07-17 17:37:56 +02:00
43afb16808 Feature/introduce isUsedByUsersWithSubscription flag (#3573) 2024-07-16 20:51:49 +02:00
d5c56fb16c Feature/optimize 7d data gathering by prioritization (#3575)
* Optimize 7d data gathering by prioritization

* Update changelog
2024-07-16 20:45:34 +02:00
b94c1f280b Bugfix/fix spacing on pricing page (#3571)
* Fix spacing
2024-07-16 20:42:41 +02:00
acc59866a3 Bugfix/fix table sorting of holdings (#3572)
* Hide holdings table to fix sorting

* Update changelog
2024-07-15 15:14:34 +02:00
c9fc3e402d Release 2.96.0 (#3570) 2024-07-13 20:13:53 +02:00
6c1317f978 Bugfix/fix search for holding in assistant (#3569)
* Fix search for holding

* Update changelog
2024-07-13 20:11:40 +02:00
89be438e66 Bugfix/remove show condition of experimental features setting (#3568)
* Remove show condition of experimental feature setting

* Update changelog
2024-07-13 19:02:47 +02:00
9d6214e93a Bugfix/fix fees calculation in portfolio summary (#3567)
* Fix fees calculation

* Update changelog
2024-07-13 18:24:03 +02:00
0640b24290 Feature/improve site.webmanifest (#3564)
* Separate icon purposes

* Update changelog
2024-07-13 11:40:45 +02:00
6eb9d9d973 Feature/extend personal finance tools 20240713 (#3565) 2024-07-13 11:40:29 +02:00
9ecc3176a5 Feature/improve treemap chart for holdings (#3563)
* Various improvements

* Introduce permission: accessHoldingsChart
* Improve style of toggle
* Add border radius

* Update changelog
2024-07-13 10:45:10 +02:00
96434c5a54 Release 2.95.0 (#3561) 2024-07-12 21:04:38 +02:00
4063c62a17 Feature/setup treemap chart for holdings (#3560)
* Setup treemap chart

* Update changelog
2024-07-12 21:02:12 +02:00
890c5b986c Feature/improve formatting of variables in README.md (#3546) 2024-07-10 17:22:47 +02:00
423bd92b89 Release 2.94.0 (#3556) 2024-07-09 18:44:53 +02:00
5dc331e386 Feature/improve language localization for de 20240709 (#3555)
* Update translations

* Update changelog
2024-07-09 18:43:20 +02:00
744dc51dcd Bugfix/fix pagination issue in activities endpoint by adding secondary sort criterion (#3554)
* Add id as secondary sort criterion to ensure consistent ordering

* Update changelog
2024-07-09 18:42:03 +02:00
b0c53d050a Feature/harmonize delete labels in admin market data (#3552) 2024-07-09 18:20:25 +02:00
830569b38e Release 2.93.0 (#3551) 2024-07-07 18:25:33 +02:00
35b4aef06f Feature/improve market state logic for forex in eod historical data service (#3550) 2024-07-07 18:23:51 +02:00
bc2fd9c970 Feature/add WTD and MTD to documentation (#3542) 2024-07-07 09:55:52 +02:00
c42a8aebed Feature/add platforms concept to faq page (#3549)
* Add concept of platforms

* Update changelog
2024-07-07 09:55:12 +02:00
fad1adb91b Feature/improve usability to delete currency asset profile (#3541)
* Improve usability

* Update changelog
2024-07-07 09:54:54 +02:00
9cd37f8de0 Feature/add crypto coins and stock heatmaps to resources page (#3548)
* Add heatmaps

* Crypto Coins Heatmap
* Stock Heatmap

* Update changelog
2024-07-07 09:40:55 +02:00
d49b90d7a5 Feature/refresh cryptocurrencies list 20240706 (#3544)
* Refresh cryptocurrencies list

* Update changelog
2024-07-07 09:39:29 +02:00
130a9ea062 Feature/remove obsolete version from docker compose files (#3543)
* Remove obsolete version

* Update changelog
2024-07-07 09:16:48 +02:00
ffc6309850 Feature/refactor thresholds of x ray rules (#3545)
* Refactor thresholds

* Update changelog
2024-07-07 08:25:51 +02:00
976cc7f243 Feature/upgrade nx to version 19.4.0 (#3540)
* Upgrade Nx to version 19.4.0

* Update changelog
2024-07-06 22:15:33 +02:00
7067aca04b Feature/replace twitter.com with x.com (#3535)
* Replace twitter.com with x.com
2024-07-05 17:26:12 +02:00
1c9805bb96 Feature/improve allocations by etf holding for impersonation mode (#3534)
* Improve allocations by ETF holding for impersonation mode

* Update changelog
2024-07-04 20:25:15 +02:00
8227a2d91a Feature/improve detection of json used via scraper configuration (#3539)
* Improve detection of json

* Update changelog
2024-07-03 18:16:07 +02:00
194aee97db Feature/update development instructions to control flow (#3466) 2024-07-02 11:58:13 +02:00
0f77169952 Fix wording (#3463) 2024-07-01 21:03:15 +02:00
0f8dc62c53 Release 2.92.0 (#3532) 2024-06-30 09:23:03 +02:00
554136cdcd Feature/bulk deletion for asset profiles (#3531)
* Add support for bulk deletion of asset profiles

* Update changelog
2024-06-30 09:21:04 +02:00
83b5cfff1f Feature/upgrade prisma to version 5.16.1 (#3526)
* Upgrade prisma to version 5.16.1

* Update changelog
2024-06-29 17:06:21 +02:00
dcec3accf0 Feature/improve caching of benchmarks (#3530)
* Improve caching

* Update changelog
2024-06-29 16:53:35 +02:00
f08b0b570b Feature/support derived currencies in currency validation (#3529)
* Support derived currencies in currency validation

* Update changelog
2024-06-29 16:30:40 +02:00
8386fec98a Feature/automatic deletion of unused asset profiles (#3525)
* Automatic deletion of unused asset profiles

* Update changelog
2024-06-29 10:53:25 +02:00
4d3dff3e5b Feature/extend personal finance tools 20240629 (#3528)
* Add Anlage.App

* Add Portfoloo

* Add SharesMaster

* Add Merlin

* Add Holistic

* Add AlphaTrackr

* Add Segmio
2024-06-29 10:53:08 +02:00
76890e63fa Bugfix/fix all time high in benchmarks (#3527)
* Fix all time high

* Update changelog
2024-06-29 10:03:45 +02:00
4fb2aebf4f Release 2.91.0 (#3522) 2024-06-26 20:40:29 +02:00
ed5cd3b978 Feature/upgrade angular to version 18.0.4 (#3520)
* Upgrade Angular to version 18.0.4

* Update changelog
2024-06-26 20:38:26 +02:00
469c1936b4 Bugfix/fix horizontal overflow in historical market data table of admin control panel (#3515)
* Fix horizontal overflow

* Update changelog
2024-06-26 20:38:12 +02:00
8b3cc5c11a Bugfix/fix dialog position on mobile (#3521)
* Fix dialog position on mobile

* Update changelog
2024-06-26 20:19:25 +02:00
ee086638f3 Feature/add benchmarks preset to admin control panel (#3513)
* Add benchmarks preset

* Update changelog
2024-06-26 20:18:53 +02:00
58d1abbd38 Feature/clean up imports (#3514)
* Clean up imports
2024-06-25 19:52:07 +02:00
ba979cbae2 Bugfix/fix addition of manual asset without market data (#3516)
* Provide default value

* Update changelog
2024-06-24 21:24:03 +02:00
8cda43bb63 Bugfix/persist intraday market data only if market state is open (#3509)
* Persist INTRADAY data only if market state is open

* Update changelog
2024-06-23 10:23:03 +02:00
c4499df74c Feature/add wealthy tracker (#3510)
* Add Wealthy Tracker
2024-06-23 10:22:35 +02:00
24bcc15b6a Release 2.90.0 (#3508) 2024-06-22 09:58:19 +02:00
ff121243e4 Feature/extend asset profile for currency (#3495)
* Extend asset profile for currency

* Update changelog
2024-06-22 09:54:23 +02:00
70e633b997 Feature/upgrade ngx device detector to version 8.0.0 (#3505)
* Upgrade ngx-device-detector to version 8.0.0

* Update changelog
2024-06-21 11:05:35 +02:00
0780ee4adb Feature/improve language localization for de 20240620 (#3504)
* Update translations

* Update changelog
2024-06-20 20:45:56 +02:00
09613f9324 Feature/extend self hosting faq by mobile app question (#3500)
* Add question about mobile app

* Update changelog
2024-06-20 17:45:31 +02:00
8642b1a7af Feature/upgrade zone.js to version 0.14.7 (#3501)
* Upgrade zone.js to version 0.14.7

* Update changelog
2024-06-19 13:52:05 +02:00
f96f861341 Feature/upgrade ngx markdown to version 18.0.0 (#3498)
* Upgrade ngx-markdown to version 18.0.0

* Update changelog
2024-06-18 20:53:03 +02:00
a201fc7a97 Feature/upgrade stripe dependencies 20240617 (#3499)
* Upgrade Stripe dependencies

* Update changelog
2024-06-18 20:37:02 +02:00
a97110348c Feature/move active filters indicator to general availability (#3485)
* Move to general availability

* Update changelog
2024-06-18 20:11:49 +02:00
a25d5b9dc0 Feature/improve error handling in biometric authentication registration (#3496)
* Improve error handling in biometric authentication registration

* Update changelog
2024-06-17 16:41:12 +02:00
6c2acf2aa6 Feature/set up ssl for local development (#3482)
* Set up SSL for local development

* Update changelog
2024-06-15 10:53:20 +02:00
519827045a Feature/add dialog for benchmarks in markets overview (#3493)
* Add benchmarks dialog in markets overview

* Update changelog
2024-06-15 09:49:54 +02:00
79a7e12a9f Release 2.89.0 (#3491) 2024-06-14 03:43:47 +02:00
bf20a5de82 Feature/improve date validation in activity endpoints (#3489)
* Improve date validation

* Update changelog
2024-06-14 03:40:47 +02:00
0adefe14e1 Feature/improve language localization (#3487)
* Update translations
2024-06-13 12:08:15 +02:00
f24561cc3d Feature/improve style of personal finance tools list (#3486) 2024-06-13 12:07:42 +02:00
873fd53715 Feature/extend market data with currencies preset by activities count and date (#3460)
* Extend market data with currencies preset by activities count and date

* Update changelog
2024-06-12 20:28:55 +02:00
e5d8faf2dc Feature/improve language localization for de 20240612 (#3483)
* Update translations

* Update changelog
2024-06-12 10:43:54 +02:00
65d3bd2802 Release 2.88.0 (#3484) 2024-06-11 20:02:21 +02:00
ad60373813 Feature/improve style of blog post list (#3481)
* Improve style

* Update changelog
2024-06-11 20:00:41 +02:00
b725e6e2ec Feature/migrate client to control flow (#3475)
* Migrate to control flow

* Update changelog
2024-06-11 19:46:16 +02:00
88c420ca5e Feature/improve language localization for de 20240611 (#3480)
* Update translations

* Update changelog
2024-06-11 19:20:13 +02:00
118e17f78c Feature/improve wording on allocations page (#3479) 2024-06-11 11:58:26 +02:00
cc92592d86 Feature/refactor personal finance tools section (#3478)
* Refactoring
2024-06-11 11:57:24 +02:00
46eb3254a9 Feature/set image source label in Dockerfile (#3477)
* Set image source label in Dockerfile

* Update changelog
2024-06-10 17:42:17 +02:00
2477491f18 Feature/upgrade nx to version 19.2.2 (#3474)
* Upgrade Nx to version 19.2.2 and Angular to version 18.0.2

* Update changelog
2024-06-09 10:24:16 +02:00
5fc9fde129 Release 2.87.0 (#3473) 2024-06-08 19:33:21 +02:00
00e50c6abe Feature/improve portfolio summary (#3472)
* Improve portfolio summary

* Update changelog
2024-06-08 19:31:50 +02:00
8131a7ad03 Feature/improve error handling in http response interceptor (#3471)
* Improve error handling in HttpResponseInterceptor

* Update changelog
2024-06-08 19:30:03 +02:00
f5e6f7dcfe Bugfix/fix initialization of fire calculator (#3470)
* Fix initialization

* Update changelog
2024-06-08 18:44:57 +02:00
87501e094d Feature/improve allocations by ETF holding for mobile (#3469) 2024-06-08 17:20:38 +02:00
d3bfdf78c3 Feature/upgrade prisma to version 5.15.0 (#3462)
* Upgrade prisma to version 5.15.0

* Update changelog
2024-06-08 16:15:36 +02:00
fc4e6ae6db Feature/improve language localization for de 20240608 (#3468)
* Update translations

* Update changelog
2024-06-08 15:51:20 +02:00
23e4d5454d Feature/improve allocations by etf holding (#3467)
* Improve allocations by ETF holding

* Update changelog
2024-06-08 11:17:27 +02:00
fdcf5fd396 Release 2.86.0 (#3465) 2024-06-07 21:47:18 +02:00
8a9ae9bb33 Feature/allocations by etf holding (#3464)
* Setup allocations by ETF holding

* Update changelog
2024-06-07 21:45:07 +02:00
3fb7e746df Feature/upgrade prettier to version 3.3.1 (#3459)
* Upgrade prettier to version 3.3.1

* Update changelog
2024-06-07 12:12:34 +02:00
137e8e090a Release 2.85.0 (#3461) 2024-06-06 17:25:08 +02:00
6d941500cd Bugfix/fix default locale in value component (#3454)
* Fix default locale

* Update changelog
2024-06-06 17:22:23 +02:00
9c4e22978d Feature/update translations (#3458) 2024-06-05 17:40:39 +02:00
af65a99398 Feature/extend personal finance tools 20240604 (#3457)
* Add Koyfin

* Add Navexa

* Add Stonksfolio
2024-06-05 17:32:43 +02:00
1a0cb561cd Feature/add ability to close user account (#3444)
* Add ability to close user account

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-06-04 15:07:06 +02:00
9f875adf0c Feature/improve language localization for de 20240602 (#3452)
* Update translations

* Update changelog
2024-06-03 20:26:58 +02:00
6b0dadb895 Feature/upgrade ng extract i18n merge to version 2.12.0 (#3449)
* Upgrade ng-extract-i18n-merge to version 2.12.0

* Update changelog
2024-06-02 10:22:31 +02:00
98a9523eee Feature/extend personal finance tools (#3448) 2024-06-02 10:20:09 +02:00
dfb3365efb Release 2.84.0 (#3447) 2024-06-01 11:16:13 +02:00
0e08d8830e Handle reduce of empty array (#3446) 2024-06-01 11:14:34 +02:00
69d85eadfd Feature/setup cascading on delete for various relations in database schema (#3445)
* Setup cascading on delete

* Update changelog
2024-06-01 11:08:36 +02:00
c009f8c12f Feature/add data provider info to asset profile details dialog (#3434)
* Add data provider info to asset profile details dialog

* Update changelog
2024-06-01 10:55:42 +02:00
60ef46accf Bugfix/fix state handling of currency selector component (#2795) (#3429)
* Fix state handling of currency selector component

* Update changelog
2024-06-01 10:53:02 +02:00
b12ac1fe84 Feature/simplify module imports of api (#3443)
* Simplify module imports
2024-06-01 10:02:43 +02:00
4355c96ab6 Bugfix/fix initial annual interest rate in fire calculator (#3437)
* Fix initial annual interest rate

* Update changelog
2024-05-31 17:29:19 +02:00
fb326fe0cc Release 2.83.0 (#3442) 2024-05-30 20:47:43 +02:00
02cfebd98c Feature/upgrade yahoo finance2 to version 2.11.3 (#3441)
* Upgrade yahoo-finance2 to version 2.11.3

* Update changelog
2024-05-30 20:45:32 +02:00
dd2936d703 Feature/upgrade countup.js to version 2.8.0 (#3436)
* Upgrade countup.js to version 2.8.0

* Update changelog
2024-05-29 16:06:58 +02:00
918d0b85d4 Feature/update passport dependencies (#3433)
* Update passport dependencies

* Refactor Google strategy

* Update changelog
2024-05-28 13:47:45 +02:00
a061595101 Feature/upgrade class validator to version 0.14.1 (#3431)
* Upgrade class-validator to version 0.14.1

* Update changelog
2024-05-27 13:55:24 +02:00
dcd496ac50 Feature/upgrade angular to version 17.3.10 (#3430)
* Upgrade angular to version 17.3.10

* Update changelog
2024-05-26 14:13:43 +02:00
6b9ec549da Feature/upgrade prisma to version 5.14.0 (#3423)
* Upgrade prisma to version 5.14.0

* Update changelog
2024-05-24 15:36:36 +02:00
b5bd4df483 Feature/upgrade nx to version 19.0.5 (#3422)
* Upgrade Nx to version 19.0.5

* Update changelog
2024-05-23 20:48:26 +02:00
766a2d7c2f Release 2.82.0 (#3425) 2024-05-22 19:53:20 +02:00
6dabf7516a Feature/preselect account in create or update activity dialog (#3413)
* Preselect account if there is only one

* Update changelog
2024-05-22 18:15:08 +02:00
5d49ff7a4a Feature/improve usability of date range selector in assistant (#3409)
* Improve usability of date range selector

* Update changelog
2024-05-22 16:39:39 +02:00
8998c18836 Feature/upgrade internationalized number to version 3.5.2 (#3412)
* Upgrade @internationalized/number to version 3.5.2

* Update changelog
2024-05-22 16:36:50 +02:00
741a0e36d2 Add links to tagged issues (#3405)
* help wanted
* good first issue
2024-05-20 22:28:43 +03:00
e31b4c64cb Feature/refactor holding detail dialog to standalone (#3407)
* Refactor holding detail dialog to standalone

* Update changelog
2024-05-19 19:13:14 +03:00
812ff5cbdc Feature/refresh cryptocurrencies list 20240514 (#3411)
* Refresh cryptocurrencies list

* Update changelog
2024-05-18 17:40:25 +03:00
035b90a689 Feature/upgrade zone.js to version 0.14.5 (#3410)
* Upgrade zone.js to version 0.14.5

* Update changelog
2024-05-17 18:08:37 +03:00
5ea3a187f4 Feature/upgrade body parser to version 1.20.2 (#3406)
* Upgrade body-parser to version 1.20.2

* Update changelog
2024-05-16 17:26:27 +02:00
5d9c38663d Feature/migrate various pages to standalone components (#3404)
* Migrate to standalone components

* Update changelog
2024-05-15 12:04:07 +02:00
5616bc4956 Feature/validate account balance creation/update using DTO (#3400)
* Validate create account balance using DTO
2024-05-13 13:47:33 +02:00
9ad1c2177c Release 2.81.0 (#3403) 2024-05-12 10:42:04 +02:00
15bf9f2f9c Feature/add Türkçe to footer (#3401) 2024-05-12 10:34:33 +02:00
ebc5008569 Feature/improve language localization for de 20240512 (#3402)
* Update translations

* Update changelog
2024-05-12 10:25:59 +02:00
37759ba03f Feature/improve language localization for tr 20240501 (#3355)
* Improve translations

* Update changelog

---------

Co-authored-by: sadmimye <134071831+sadmimye@users.noreply.github.com>
2024-05-12 09:58:11 +02:00
8319b216bb Feature/support delete activities with filtering (#3394)
* Support delete activities with filtering

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-05-12 09:56:07 +02:00
782d131b0d Feature/add indicator for active filters (#3398)
* Add indicator for active filters

* Update changelog
2024-05-12 09:42:27 +02:00
72e75208df Bugfix/fix position detail dialog close functionality (#3396)
* Handle holding detail dialog open functionality in a single place (AppComponent)

* Update changelog
2024-05-11 20:27:18 +02:00
4b1c27c245 Feature/upgrade nx to version 19.0.2 (#3391)
* Upgrade Nx dependencies to version 19.0.2

* Update changelog
2024-05-11 08:33:33 +02:00
61f0da35bc Feature/disable delete all activities if filters are active (#3389)
* Disable delete all activities button if filters are active

* Update changelog
2024-05-10 08:51:34 +02:00
80464c7846 Release 2.80.0 (#3386) 2024-05-08 20:55:39 +02:00
74f4323903 Feature/increase spacing around floating action buttons (#3385)
* Increase spacing around floating action buttons

* Update changelog
2024-05-08 20:54:13 +02:00
127dbf9dcd Update translations (#3384) 2024-05-08 20:37:53 +02:00
66bdb374e8 Feature/set icon columns of tables to stick at beginning (#3377)
* Set icon columns to stick at the beginning

* Update changelog
2024-05-08 20:04:58 +02:00
4ad4fa2b30 Feature/clean up deprecated GET api/portfolio/positions endpoint (#3373) 2024-05-08 20:04:32 +02:00
1fd836194f Feature/add absolute change column to holdings table (#3378)
* Add absolute change column

* Update changelog
2024-05-08 20:02:50 +02:00
2090db1199 Feature/increase number of attempts of queue jobs (#3376)
* Increase number of attempts

* Update changelog
2024-05-07 20:48:02 +02:00
053c7e591e Feature/upgrade ionicons to version 7.4.0 (#3356)
* Upgrade ionicons to version 7.4.0

* Update changelog
2024-05-07 19:00:55 +02:00
9b5e350e3b Feature/harmonize log message (#3343) 2024-05-06 17:03:58 +02:00
378e57c3bc Feature/add links to Home Assistant add-on (#3367)
* Add links to Home Assistant add-on
2024-05-06 17:02:18 +02:00
6765191a8c Bugfix/fix position detail dialog open in holding search of assistant (#3374)
* Open position detail dialog (via holding search of assistant)

* Update changelog
2024-05-05 09:09:57 +02:00
8438a45bcf Update changelog (#3372) 2024-05-04 16:32:32 +02:00
30a64e7fc1 Release 2.79.0 (#3371) 2024-05-04 15:54:29 +02:00
f2cb671c7f Feature/optimize get porfolio details endpoint (#3366)
* Eliminate getPerformance() from getSummary() function

* Disable cache for getDetails()

* Add hint to portfolio summary

* Update changelog
2024-05-04 15:53:02 +02:00
3f41e5c5de Bugfix/fix locale in markets overview (#3369)
* Fix locale if no user is logged in

* Update changelog
2024-05-04 15:51:09 +02:00
c1ad483f33 Improve alignment (#3370) 2024-05-04 15:50:47 +02:00
f3d961bc16 Feature/move holdings table to holdings tab of home page (#3368)
* Move holdings table to holdings tab of home page

* Deprecate api/v1/portfolio/positions endpoint

* Update changelog
2024-05-04 14:11:37 +02:00
42b70ef568 Feature/improve performance labels in position detail dialog (#3363)
* Improve performance labels (with and without currency effects)

* Update changelog
2024-05-04 07:49:37 +02:00
77beaaba08 Refactoring portfolio service (#3365) 2024-05-03 21:48:46 +02:00
d9c07456cd Release 2.78.0 (#3361) 2024-05-02 20:32:47 +02:00
0a53df4293 Feature/improve inactive user role (#3360)
* Improve inactive role

* Update changelog
2024-05-02 20:31:20 +02:00
4416ba0c88 Feature/set performance column of holdings table to stick at end (#3353)
* Set up stickyEnd in performance column

* Update changelog
2024-05-02 19:16:59 +02:00
486de968a2 Bugfix/fix division by zero error in dividend yield calculation (#3354)
* Handle division by zero

* Update changelog
2024-05-02 17:53:34 +02:00
a5833566a8 Feature/skip caching in portfolio calculator if active filters (#3348)
* Skip caching if active filters

* Update changelog
2024-05-02 17:52:39 +02:00
261f5844dd Add type column to README.md (#3295) 2024-04-30 14:08:14 +02:00
2173c418a7 Feature/validate forms using DTO for access, asset profile, tag and platform management (#3337)
* Validate forms using DTO for access, asset profile, tag and platform management

* Update changelog
2024-04-30 08:04:45 +02:00
4efd5cefd8 Bugfix/calculation of portfolio summary caused by future liabilities (#3342)
* Adapt date of future activities

* Update changelog
2024-04-29 20:12:12 +02:00
d735e4db75 Release 2.77.1 (#3340) 2024-04-27 19:25:23 +02:00
e10707fde4 Add missing guard to fix public page (#3339) 2024-04-27 19:24:08 +02:00
ac953df809 Release 2.77.0 (#3338) 2024-04-27 15:37:48 +02:00
bb4ee50738 Feature/update browserslist database 20240417 (#3288)
* Update the browserslist database

* Update changelog
2024-04-27 15:35:57 +02:00
4f41bac328 Feature/set up caching in portfolio calculator (#3335)
* Set up caching

* Update changelog
2024-04-27 15:35:28 +02:00
cd07802400 Bugfix/fix historical market data gathering for asset profiles with manual data source (#3336)
* Fix historical market data gathering for asset profiles with MANUAL data source

* Update changelog
2024-04-27 15:29:32 +02:00
b692b7432c Feature/set up event system for portfolio changes (#3333)
* Set up event system for portfolio changes

* Update changelog
2024-04-26 20:13:53 +02:00
a4efbc0131 Feature/migrate UI components to control flow (#3324)
* Migrate to control flow

* Update changelog
2024-04-26 17:40:00 +02:00
55b0fe232c Bugfix/reset form values to null if empty string (#3327)
* Reset form values to null if empty string

* Update changelog
2024-04-25 19:04:28 +02:00
46432edce9 Feature/extend faq by custom asset instructions (#3326)
* Add instructions for custom asset

* Update changelog
2024-04-24 20:35:39 +02:00
990028316e Refactor form controls to form getter (#3325) 2024-04-24 20:20:56 +02:00
37871fbabc Feature/upgrade prisma to version 5.13.0 (#3323)
* Upgrade prisma to version 5.13.0

* Update changelog
2024-04-24 20:20:09 +02:00
0fdeef7953 Release 2.76.0 (#3322) 2024-04-23 18:58:43 +02:00
cdbe6eedeb Feature/change cash to liquidity in asset class enum (#3321)
* Change CASH to LIQUIDITY in asset class enum

* Update changelog
2024-04-23 18:55:37 +02:00
a6dde8ad43 Release 2.75.1 (#3317) 2024-04-21 17:14:35 +02:00
39bd4a349b Feature/improve chart in account detail dialog (#3314)
* Improve net worth calculation in portfolio performance chart

* Improve account balance management

* Update changelog
2024-04-21 17:11:53 +02:00
ab59eb5c92 Release 2.75.0 (#3316) 2024-04-21 10:30:14 +02:00
1132dc9bdd Feature/add unique constraint to account balance database schema (#3315)
* Add accountId and date as unique constraint to AccountBalance schema

* Update changelog
2024-04-21 10:28:51 +02:00
2d70b18593 Remove links (#3306) 2024-04-21 10:08:22 +02:00
b6ea7d23fa Bugfix/add total value in base currency to redacted values (#3313)
* Add totalValueInBaseCurrency

* Update changelog
2024-04-21 08:33:38 +02:00
95382581f1 Release 2.74.0 (#3312) 2024-04-20 15:43:03 +02:00
551b83a6e3 Bugfix/fix gaps in portfolio performance charts (#3311)
* Fix gaps in charts

* Update changelog
2024-04-20 15:40:59 +02:00
895c4fe299 Improve style on mobile (#3310) 2024-04-20 12:54:23 +02:00
ccb1bf881e Feature/improve language localization for de 20240420 (#3309)
* Update translations

* Update changelog
2024-04-20 12:54:06 +02:00
7788b5a987 Improve messages (#3308) 2024-04-20 12:53:51 +02:00
47fd029e0c Clean up styles (#3305) 2024-04-20 12:15:21 +02:00
458ee159e1 Feature/upgrade nx to version 18.3.3 (#3307)
* Upgrade angular and Nx dependencies

* Update changelog
2024-04-20 12:14:50 +02:00
dfacbed66d Feature/add support to create account cash balances (#3260)
* Add support to create account cash balances

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-04-20 11:00:00 +02:00
22d63c6102 Improve logging (#3304)
* Improve logging
2024-04-20 10:49:18 +02:00
212aa6a63b Feature/add date range support to portfolio holdings page (#3303)
* Add date range support

* Update changelog
2024-04-20 09:51:12 +02:00
bed9ae916c Migrate UI components to standalone (#3302) 2024-04-19 20:57:47 +02:00
b6ad362850 Feature/remove date range support in activities table on portfolio activities page (#3301)
* Remove date range support

* Update changelog
2024-04-19 20:44:28 +02:00
ab86dd5318 Add Home Assistant (#3291) 2024-04-19 20:43:52 +02:00
dba73d80a3 Improve logging (#3300) 2024-04-19 20:31:38 +02:00
92fb05320a Migrate UI components to standalone (#3296) 2024-04-19 18:31:35 +02:00
73d62bb51f Migrate UI components to standalone (#3290) 2024-04-18 20:46:12 +02:00
127b7d4f25 Make pre-commit executable by default (#3283) 2024-04-18 18:39:20 +02:00
e79d607ab8 Release 2.73.0 (#3287) 2024-04-17 17:42:28 +02:00
5f7d083f7c Feature/upgrade yahoo finance2 to version 2.11.2 (#3286)
* Upgrade yahoo-finance2 to version 2.11.2

* Update changelog
2024-04-17 17:40:58 +02:00
15857118fe Feature/let data gathering queue jobs fail by throwing errors (#3281)
* Let data gathering queue jobs fail by throwing errors

* Update changelog
2024-04-17 17:35:51 +02:00
ff91ed21df Upgrade @types/lodash to version 4.17.0 (#3227) 2024-04-15 19:25:46 +02:00
9241c04d5a Feature/add form validation against DTO for activity and account (#3230)
* Add form validation against DTO for activity and account

* Update changelog
2024-04-14 19:52:41 +02:00
5d4e2fba8c Feature/move wealth item and liability calculations to portfolio calculator (#3272)
* Move (wealth) item calculations to portfolio calculator

* Move liability calculations to portfolio calculator

* Update changelog
2024-04-14 08:12:32 +02:00
6c57609db8 Feature/move dividend fee and interest calculation to portfolio calculator (#3267)
* Move dividend, feee and interest calculation to portfolio calculator

* Update changelog
2024-04-13 11:07:18 +02:00
b31bbbe2d1 Release 2.72.0 (#3270) 2024-04-13 09:31:07 +02:00
7d308917dd Feature/upgrade yahoo finance2 to version 2.11.1 (#3254)
* Upgrade yahoo-finance2 to version 2.11.1

* Update changelog
2024-04-13 09:28:38 +02:00
4e7d93db13 Feature/adapt priorities of data gathering jobs (#3262)
* Adapt priorities of data gathering jobs

* Update changelog
2024-04-13 09:28:14 +02:00
45340b581f Bugfix/fix public page by including markets data (#3263)
* Include markets data

* Update changelog
2024-04-12 15:13:34 +02:00
6f8fe45fc2 Update OSS Friends (#3258) 2024-04-11 19:14:56 +02:00
34d9ceb009 Feature/add support to immediately execute queue job (#3259)
* Add support to immediately execute queue job

* Update changelog
2024-04-11 19:14:03 +02:00
2b97bbd05d Move getChart() to portfolio calculator (#3255) 2024-04-09 13:44:23 +02:00
3bf7ac76a0 Feature/upgrade nx to version 18.2.3 (#3256)
* Upgrade Nx to version 17.3.3

* Update changelog
2024-04-08 08:07:00 +02:00
71892e67b2 Feature/upgrade prisma to version 5.12.1 (#3253)
* Upgrade prisma to version 5.12.1

* Update changelog
2024-04-07 20:27:38 +02:00
6e2885ed20 Release 2.71.0 (#3252) 2024-04-07 11:32:55 +02:00
07c0e5a612 Feature/add currency to order database schema (#3251)
* Add currency to Order database schema

* Update changelog
2024-04-07 11:30:32 +02:00
719bbe156e Feature/optimize calculation of allocations by market (#3249)
* Optimize calculation of allocations by market

* Update changelog
2024-04-07 09:26:30 +02:00
b51255a543 Feature/add key to x ray rule (#3248)
* Add key

* Update changelog
2024-04-07 09:25:14 +02:00
50dbbf0569 Feature/refactor symbol icon module to asset profile icon component (#3245)
* Refactor symbol icon module to asset profile icon component (standalone)
2024-04-06 21:15:19 +02:00
ca2e748c56 Bugfix/add missing tags in portfolio calculator (#3243)
* Add missing tags

* Update changelog
2024-04-06 20:03:16 +02:00
5c480109d5 Feature/add quotes in README.md for API auth documentation (#3246) 2024-04-06 20:00:56 +02:00
6152ff4b44 Remove condition (#3242) 2024-04-06 12:50:35 +02:00
c6641fde36 Feature/add icon to create or update platform dialog (#3241)
* Add platform icon

* Update changelog
2024-04-06 09:11:15 +02:00
4ae7e9fcbe Feature/add icon to asset profile dialog (#3240)
* Add asset profile icon

* Update changelog
2024-04-06 08:42:06 +02:00
c10ae431a2 Feature/extend faq by data providers (#3239)
* Add data providers

* Update changelog
2024-04-06 08:40:44 +02:00
883e30e451 Feature/improve language localization for de 20240403 (#3236)
* Update translations

* Update changelog
2024-04-05 19:38:10 +02:00
f1f4f6247d Feature/validate url in create and update platform dto (#3235)
* Validate url

* Update changelog
2024-04-04 09:08:37 +02:00
82fe1de1a7 Feature/add support to override asset (sub) class and url in admin control panel (#3218)
* Add support to override asset (sub) class and url in admin control panel

* Update changelog
2024-04-03 20:24:38 +02:00
371c999fbc Feature/Add dividend yield to position detail dialog (#2636)
* Add dividend yield to position detail dialog

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-04-03 19:47:53 +02:00
26b9660e11 Release 2.70.0 (#3234) 2024-04-02 20:29:47 +02:00
ca7717f9c5 Bugfix/Enable tini in docker compose files instead of adding it to the Dockerfile (#3232)
* Enable tini in docker compose files instead of adding it to the Dockerfile

* Update changelog
2024-04-02 20:26:14 +02:00
6f3cce1c5f Feature/disable option to update cash balance if date is not today (#3229)
* Disable option to update cash balance if date is not today

* Update changelog
2024-04-02 20:17:16 +02:00
efdc9b387f Eliminate ghostfolio-style.scss (#3228) 2024-04-01 13:24:00 +02:00
d7b579e3e8 Feature/refactor getAnnualizedPerformancePercent to portfolio calculator (#3226)
* Move getAnnualizedPerformancePercent() to portfolio calculator
2024-04-01 10:42:15 +02:00
b8533050b0 Bugfix/fix duplicated tags in position detail dialog (#3224)
* Fix duplicated tags

* Update changelog
2024-04-01 09:22:35 +02:00
1b81409b35 Add OpenAlternative logo (#3225) 2024-04-01 09:02:10 +02:00
8cd6c34ed8 Feature/introduce portfolio calculator factory (#3214)
* Introduce portfolio calculator factory

* Update changelog
2024-03-31 20:07:58 +02:00
0c68474802 Extract locales (#3223) 2024-03-31 11:41:44 +02:00
34997f91db Feature/setup webpack bundle analyzer (#3222)
* Set up  Webpack Bundle Analyzer

* Update changelog
2024-03-31 11:38:09 +02:00
084467ee9a Feature/reverse order of specific years in date range selector of assistant (#3221)
* Reverse order

* Update changelog
2024-03-31 11:24:50 +02:00
af47889d65 Add Chinese translations (#3215)
* Add Chinese translations
2024-03-31 11:24:32 +02:00
51203ec96e Feature/setup chinese (#3220)
* Set up chinese

* Update changelog
2024-03-31 10:46:30 +02:00
a2277dea2c Release 2.69.0 (#3212) 2024-03-30 13:43:43 +01:00
debd233c32 Feature/set up Tini to avoid zombie processes (#3195)
* Set up Tini to avoid zombie processes

* Update changelog
2024-03-30 13:42:02 +01:00
f1eeee0525 Feature/extend date range support by specific years (#3190)
* Extend date range support by specific years

* Support date range in benchmark endpoint

* Support date range in activities endpoint

* Update changelog
2024-03-30 13:06:42 +01:00
5ffc39c32f Feature/improve usability to delete asset profile (#3208)
* Disable delete button for benchmarks

* Update changelog
2024-03-30 11:23:59 +01:00
a668a66e84 Feature/add missing dates to edit historical market data in asset profile details dialog (#3206)
* Add missing dates to edit historical market data in asset profile details dialog

* Update changelog
2024-03-30 08:50:06 +01:00
0581b8b9ec Release 2.68.0 (#3205) 2024-03-29 17:54:17 +01:00
63a61fb492 Move portfolio calculator (#3204)
* Move portfolio calculator
2024-03-29 17:47:44 +01:00
5788c6474e Refactor portfolio calculator (#3203)
* Refactor portfolio calculator

* Consume Activity[]
* Change computeTransactionPoints() to private
* Eliminate getTransactionPoints()

* Update changelog
2024-03-29 17:34:22 +01:00
5529fdc0ee Move transaction points to constructor (#3202) 2024-03-29 13:50:24 +01:00
88a9b518f6 Bugfix/fix issue with overridden names in activities table (#3200)
* Fix issue with overridden names

* Update changelog
2024-03-29 09:50:04 +01:00
98de2355c4 Feature/Support overriding name of asset profile dialog (#3199)
* Support overriding name of asset profile dialog

* Update changelog
2024-03-29 09:35:41 +01:00
b41eb60348 Fix chart tooltip of benchmark comparator (#3167)
* Fix chart tooltip of benchmark comparator

* Update changelog
2024-03-28 18:23:56 +01:00
0edebe30e1 Feature/Add user account currency to export (#3194)
* Add user account currency to export

* Update changelog
2024-03-27 18:08:34 +01:00
e3abe4feee Release 2.67.0 (#3197) 2024-03-26 17:45:32 +01:00
50391e199a Feature/improve generation of random strings (#3196)
* Replace Math.random() with crypto.randomBytes()

* Update changelog
2024-03-26 17:43:42 +01:00
a33f8d5bed Upgrade @types/big.js to version 6.2.2 (#3191)
* Upgrade @types/big.js to version 6.2.2

* Refactor imports
2024-03-25 08:34:30 +01:00
636be8441e Feature/upgrade zone.js to version 0.14.4 (#3184)
* Upgrade zone.js to version 0.14.4

* Update changelog
2024-03-24 10:30:06 +01:00
654dc2ba32 Feature/upgrade yahoo finance2 to version 2.11.0 (#3183)
* Upgrade yahoo-finance2 to version 2.11.0

* Update changelog
2024-03-24 09:50:45 +01:00
458ef169f4 Feature/add support for toncoin cryptocurrency (#3189)
* Add Toincoin

* Update changelog
2024-03-24 09:50:00 +01:00
5bb01bb03c Feature/upgrade ionicons to version 7.3.0 (#3182)
* Upgrade ionicons to version 7.3.0

* Update changelog
2024-03-24 09:09:09 +01:00
43e9528d8c Release 2.66.3 (#3188) 2024-03-23 20:56:22 +01:00
522c54c9b4 Release 2.66.2 (#3187) 2024-03-23 20:40:09 +01:00
0004ced4e1 Release 2.66.1 (#3186) 2024-03-23 20:26:10 +01:00
274c60e961 Release 2.66.0 (#3185) 2024-03-23 19:55:17 +01:00
754e98099c Feature/Set up tini (#3168)
* Set up tini to avoid zombie processes and perform signal forwarding in docker image

* Update changelog
2024-03-23 19:53:27 +01:00
87bf8df1c3 Bugfix/missing performance chart in presenter view (#3181)
* Fix missing performance chart in presenter view

* Update changelog
2024-03-23 19:41:28 +01:00
3f7d6b25c7 Feature/extend faq by backup strategy (#3180)
* Add backup strategy

* Update changelog
2024-03-23 19:10:23 +01:00
8a062e03ab Feature/add benchmark name to tooltip of benchmark comparator (#3177)
* Add benchmark name to chart tooltip

* Update changelog
2024-03-23 09:38:35 +01:00
a70f45cbf3 Feature/add index for data source symbol to market data table (#3179)
* Add index

* Update changelog
2024-03-23 09:23:59 +01:00
f268264c46 Feature/upgrade nx to version 18.1.2 (#3174)
* Upgrade angular and Nx dependencies

* Update changelog
2024-03-23 09:09:10 +01:00
bbe5d70720 Rename Twitter to X (#3009) 2024-03-21 15:10:35 +01:00
f1d2a52cba Release 2.65.0 (#3170) 2024-03-19 20:13:22 +01:00
87cc887865 Feature/Set meta theme color dynamically to respect appearance (#3129)
* Set meta theme color dynamically to respect appearance (dark mode)

* Update changelog
2024-03-19 20:11:34 +01:00
61ecd15f1d Feature/change edit button to a in admin market data page (#3164)
* Change edit button to a

* Update changelog
2024-03-19 19:55:08 +01:00
eb853f05ae Feature/add support to delete asset profile from dialog (#3165)
* Add support to delete asset profile from dialog

* Update changelog
2024-03-19 19:27:16 +01:00
6285417903 Feature/change grant private access with permissions to general availability (#3169)
* Change grant private access with permissions from experimental to general availability

* Update changelog
2024-03-19 19:25:43 +01:00
ca674a654e Feature/add symbol and isin to position detail dialog (#3163)
* Add symbol and ISIN

* Update translations

* Update changelog
2024-03-18 13:58:46 +01:00
2729c5651f Release 2.64.0 (#3161) 2024-03-16 19:00:50 +01:00
7e28e42995 Feature/exclude fees from holdings (#3160) 2024-03-16 18:59:23 +01:00
e21563d903 Feature/increase timeout to load benchmarks (#3158)
* Increase request timeout

* Update changelog
2024-03-16 18:15:34 +01:00
3ede69650c Feature/upgrade prisma to version 5.11.0 (#3159)
* Upgrade prisma to version 5.11.0

* Update changelog
2024-03-16 16:51:07 +01:00
c289793c6d Feature/switch between active and closed holdings (#3146)
* Switch between active and closed holdings on the portfolio holdings page

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-03-16 14:20:58 +01:00
a90c067da0 Clean up (#3157) 2024-03-16 13:40:23 +01:00
38c2baf943 Feature/improve exception handling of current investments in various rules (#3156)
* Improve exception handling

* Update changelog
2024-03-16 12:29:21 +01:00
82c78cad6b Format (#3155) 2024-03-16 12:28:58 +01:00
bffe6060bd Pass portfolio calculator to getChart() (#3153) 2024-03-16 10:05:16 +01:00
841bd5c33f Bugfix/fix dividend accumulation in symbol metrics (#3152)
* Fix total dividend calculation

* Update changelog
2024-03-16 10:04:57 +01:00
3b895afc9e Enable account balance update for fee and interest activities (#3145)
* Enable account balance update for fee and interest activities

* Update changelog
2024-03-15 18:56:17 +01:00
00c2ede85e Feature/improve usability of platform and tag management (#3144)
* Improve usability

* Update changelog
2024-03-15 08:37:41 +01:00
8420cb830c Feature/add product roadmap to faq (#3143)
* Add product roadmap

* Update changelog
2024-03-14 14:09:20 +01:00
a0ddd1f9b9 Fix date conversion in import of historical market data (#3117)
* Fix date conversion in import of historical market data

* Update changelog
2024-03-13 20:44:33 +01:00
40d93066ff Introduce .env.dev (#3120) 2024-03-13 20:21:54 +01:00
671e4e316b Release 2.63.2 (#3138) 2024-03-12 20:50:32 +01:00
473136e9aa Release 2.63.1 (#3135)
* Release 2.63.1
2024-03-11 21:35:43 +01:00
9a3db91982 Release 2.63.0 (#3134) 2024-03-11 20:19:23 +01:00
d23cb5f190 Feature/upgrade simplewebauthn dependencies to version 9.0 (#3130)
* Upgrade @simplewebauthn/browser and @simplewebauthn/server to version 9.0

* Update changelog
2024-03-11 20:17:47 +01:00
7a364472c8 Bugfix/fix liability issue in allocations (#3133)
* Remove liabilities from allocations calculation

* Update changelog
2024-03-11 20:16:56 +01:00
59c064e3c8 Feature/upgrade yahoo finance2 to version 2.10.0 (#3127)
* Upgrade yahoo-finance2 to version 2.10.0

* Update changelog
2024-03-11 20:15:55 +01:00
e792924606 Update OSS friends (#3132) 2024-03-11 20:15:32 +01:00
d32dd5e860 Feature/upgrade countries list to version 3.1.0 (#3131)
* Upgrade countries-list to version 3.1.0

* Update changelog
2024-03-11 19:16:20 +01:00
bb86f85203 Feature/add available home server systems to faq (#3126)
* Add available home server systems

* Update changelog

* Add CasaOS to README.md
2024-03-10 09:50:43 +01:00
0bca8897d6 Fix average price calculation by only considering BUY transactions (#3125)
* Fix average price calculation by only considering buy transactions

* Update changelog
2024-03-10 09:35:47 +01:00
ba73f6de2e Release 2.62.0 (#3124) 2024-03-09 19:57:56 +01:00
eb75be8535 Optimize details endpoint (#3123)
* Make summary optional

* Introduce dedicated holdings endpoint

* Update changelog
2024-03-09 19:56:26 +01:00
6d2a897366 Refactor orders with activities (#3122) 2024-03-09 17:17:52 +01:00
d8bfb23f20 Refactor reduce() with getSum() (#3121) 2024-03-09 16:53:59 +01:00
d9d71e7827 Fix issue with removing account from activity (#3112)
* Fix issue with removing account from activity

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-03-09 15:52:05 +01:00
b642ce08e5 Refactor item type (#3119) 2024-03-09 12:32:56 +01:00
bc8d8309d4 Improve handling of future liabilities (#3118)
* Improve handling of future liabilities

* Refactor currentValue to currentValueInBaseCurrency

* Update changelog
2024-03-09 11:07:01 +01:00
1f2f9f22f2 Feature/remove environment variable web auth rp (#3115)
* Remove environment variable WEB_AUTH_RP_ID

* Update changelog
2024-03-08 19:00:21 +01:00
7a3237f1ff Adapt style of inactive users (#3114) 2024-03-08 18:59:23 +01:00
07661d9262 Feature/integrate dividend into transaction point concept (#3092)
* Integrate dividend into transaction point concept

* Update changelog
2024-03-07 20:07:50 +01:00
77358eed65 Feature/Include user role in admin endpoint (#3107)
* Include user role in admin endpoint
2024-03-07 19:38:57 +01:00
c641c28b12 Release 2.61.1 (#3110) 2024-03-06 22:08:00 +01:00
c54392b7bb Bugfix/fix exception in account value calculation (#3109)
* Fix exception in value of account calculation caused by liabilities

* Update changelog
2024-03-06 22:06:27 +01:00
f3a8822a77 Feature/remove-v-from-version-in-admin-endpoint (#3101)
* Remove "v" from version in admin endpoint
2024-03-06 11:11:10 +01:00
f1dc075c36 Update translations (#3093) 2024-03-05 10:16:59 +01:00
1092 changed files with 142153 additions and 134611 deletions

24
.env.dev Normal file
View File

@ -0,0 +1,24 @@
COMPOSE_PROJECT_NAME=ghostfolio-development
# CACHE
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=<INSERT_REDIS_PASSWORD>
# POSTGRES
POSTGRES_DB=ghostfolio-db
POSTGRES_USER=user
POSTGRES_PASSWORD=<INSERT_POSTGRES_PASSWORD>
# VARIOUS
ACCESS_TOKEN_SALT=<INSERT_RANDOM_STRING>
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
JWT_SECRET_KEY=<INSERT_RANDOM_STRING>
# DEVELOPMENT
# Nx 18 enables using plugins to infer targets by default
# This is disabled for existing workspaces to maintain compatibility
# For more info, see: https://nx.dev/concepts/inferred-tasks
NX_ADD_PLUGINS=false

View File

@ -1,7 +1,7 @@
COMPOSE_PROJECT_NAME=ghostfolio-development
COMPOSE_PROJECT_NAME=ghostfolio
# CACHE
REDIS_HOST=localhost
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=<INSERT_REDIS_PASSWORD>
@ -10,6 +10,7 @@ POSTGRES_DB=ghostfolio-db
POSTGRES_USER=user
POSTGRES_PASSWORD=<INSERT_POSTGRES_PASSWORD>
# VARIOUS
ACCESS_TOKEN_SALT=<INSERT_RANDOM_STRING>
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
JWT_SECRET_KEY=<INSERT_RANDOM_STRING>

View File

@ -1,118 +0,0 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {}
},
{
"files": ["*.ts"],
"plugins": ["eslint-plugin-import", "@typescript-eslint"],
"rules": {
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/explicit-member-accessibility": [
"off",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-inferrable-types": [
"error",
{
"ignoreParameters": true
}
],
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-shadow": [
"error",
{
"hoist": "all"
}
],
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "off",
"constructor-super": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"id-blacklist": "off",
"id-match": "off",
"import/no-deprecated": "warn",
"no-bitwise": "error",
"no-caller": "error",
"no-console": [
"error",
{
"allow": [
"log",
"warn",
"dir",
"timeLog",
"assert",
"clear",
"count",
"countReset",
"group",
"groupEnd",
"table",
"dirxml",
"error",
"groupCollapsed",
"Console",
"profile",
"profileEnd",
"timeStamp",
"context"
]
}
],
"no-debugger": "error",
"no-empty": "off",
"no-eval": "error",
"no-fallthrough": "error",
"no-new-wrappers": "error",
"no-restricted-imports": ["error", "rxjs/Rx"],
"no-throw-literal": "error",
"no-undef-init": "error",
"no-underscore-dangle": "off",
"no-var": "error",
"prefer-const": "error",
"radix": "error"
}
}
],
"extends": [null, "plugin:storybook/recommended"]
}

View File

@ -26,7 +26,7 @@ Thank you for your understanding and cooperation!
2.
3.
**Expected behavior**
**Expected Behavior**
<!-- A clear and concise description of what you expected to happen. -->
@ -48,6 +48,6 @@ Thank you for your understanding and cooperation!
- Browser
- OS
**Additional context**
**Additional Context**
<!-- Add any other context about the problem here. -->

View File

@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
node_version:
- 18
- 22
steps:
- name: Checkout code
uses: actions/checkout@v4
@ -24,16 +24,19 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: 'yarn'
cache: 'npm'
- name: Install dependencies
run: yarn install --frozen-lockfile
run: npm ci
- name: Check code style
run: npm run lint
- name: Check formatting
run: yarn format:check
run: npm run format:check
- name: Execute tests
run: yarn test
run: npm test
- name: Build application
run: yarn build:production
run: npm run build:production

View File

@ -4,9 +4,12 @@ on:
push:
tags:
- '*.*.*'
branches:
- 'main'
pull_request:
branches:
- 'main'
workflow_dispatch:
jobs:
build_and_push:
@ -15,14 +18,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Docker metadata
- name: Get Meta
id: meta
uses: docker/metadata-action@v4
with:
images: ghostfolio/ghostfolio
tags: |
type=semver,pattern={{major}}
type=semver,pattern={{version}}
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
@ -35,16 +35,36 @@ jobs:
if: github.event_name != 'pull_request'
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
registry: gitea.suda.codes
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
# platforms: linux/amd64,linux/arm/v7,linux/arm64
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.output.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
gitea.suda.codes/sudacode/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
gitea.suda.codes/sudacode/${{ steps.meta.outputs.REPO_NAME }}:latest
cache-from: type=local,src=${{ runner.temp }}/.buildx-cache
cache-to: type=local,dest=${{ runner.temp }}/.buildx-cache-new,mode=max
- # Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf ${{ runner.temp }}/.buildx-cache
mv ${{ runner.temp }}/.buildx-cache-new ${{ runner.temp }}/.buildx-cache
- name: Invoke deployment hook
uses: distributhor/workflow-webhook@v3
with:
webhook_url: ${{ secrets.WEBHOOK_URL }}
webhook_auth: ${{ secrets.WEBHOOK_AUTH }}
webhook_secret: ${{ secrets.WEBHOOK_SECRET }}
webhook_auth_type: bearer

9
.gitignore vendored
View File

@ -1,12 +1,14 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
scripts/*
# compiled output
/out-tsc
/tmp
# dependencies
/.yarn
/node_modules
npm-debug.log
# IDEs and editors
/.idea
@ -25,18 +27,19 @@
# misc
/.angular/cache
.cursor/rules/nx-rules.mdc
.env
.env.prod
.github/instructions/nx.instructions.md
.nx/cache
.nx/workspace-data
/.sass-cache
/connect.lock
/coverage
/dist
/libpeerconnection.log
npm-debug.log
testem.log
/typings
yarn-error.log
# System Files
.DS_Store

6
.husky/pre-commit Normal file
View File

@ -0,0 +1,6 @@
# Run linting and stop the commit process if any errors are found
# --quiet suppresses warnings (temporary until all warnings are fixed)
npm run affected:lint --base=main --head=HEAD --parallel=2 --quiet || exit 1
# Check formatting on modified and uncommitted files, stop the commit if issues are found
npm run format:check --uncommitted || exit 1

2
.nvmrc
View File

@ -1 +1 @@
v18
v22

View File

@ -1,4 +1,5 @@
/.nx/cache
/.nx/workspace-data
/apps/client/src/polyfills.ts
/dist
/test/import

View File

@ -1,4 +1,7 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
"editor.formatOnSave": true,
"vim.highlightedyank.enable": true,
"vim.hlsearch": true,
"vim.leader": "<space>",
}

View File

@ -1 +0,0 @@
network-timeout 600000

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,59 @@
# Ghostfolio Development Guide
## Development Environment
### Prerequisites
- [Docker](https://www.docker.com/products/docker-desktop)
- [Node.js](https://nodejs.org/en/download) (version 22+)
- Create a local copy of this Git repository (clone)
- Copy the file `.env.dev` to `.env` and populate it with your data (`cp .env.dev .env`)
### Setup
1. Run `npm install`
1. Run `docker compose -f docker/docker-compose.dev.yml up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
1. Run `npm run database:setup` to initialize the database schema
1. Start the [server](#start-server) and the [client](#start-client)
1. Open https://localhost:4200/en in your browser
1. Create a new user via _Get Started_ (this first user will get the role `ADMIN`)
### Start Server
#### Debug
Run `npm run watch:server` and click _Debug API_ in [Visual Studio Code](https://code.visualstudio.com)
#### Serve
Run `npm run start:server`
### Start Client
#### English (Default)
Run `npm run start:client` and open https://localhost:4200/en in your browser.
#### Other Languages
To start the client in a different language, such as German (`de`), adapt the `start:client` script in the `package.json` file by changing `--configuration=development-en` to `--configuration=development-de`. Then, run `npm run start:client` and open https://localhost:4200/de in your browser.
### Start _Storybook_
Run `npm run start:storybook`
### Migrate Database
With the following command you can keep your database schema in sync:
```bash
npm run database:push
```
## Testing
Run `npm test`
## Experimental Features
New functionality can be enabled using a feature flag switch from the user settings.
@ -10,7 +64,11 @@ Remove permission in `UserService` using `without()`
### Frontend
Use `*ngIf="user?.settings?.isExperimentalFeatures"` in HTML template
Use `@if (user?.settings?.isExperimentalFeatures) {}` in HTML template
## Component Library (_Storybook_)
https://ghostfol.io/development/storybook
## Git
@ -30,26 +88,35 @@ Use `*ngIf="user?.settings?.isExperimentalFeatures"` in HTML template
#### Upgrade
1. Run `yarn nx migrate latest`
1. Make sure `package.json` changes make sense and then run `yarn install`
1. Run `yarn nx migrate --run-migrations` (Run `YARN_NODE_LINKER="node-modules" NX_MIGRATE_SKIP_INSTALL=1 yarn nx migrate --run-migrations` due to https://github.com/nrwl/nx/issues/16338)
1. Run `npx nx migrate latest`
1. Make sure `package.json` changes make sense and then run `npm install`
1. Run `npx nx migrate --run-migrations`
### Prisma
#### Access database via GUI
Run `yarn database:gui`
Run `npm run database:gui`
https://www.prisma.io/studio
#### Synchronize schema with database for prototyping
Run `yarn database:push`
Run `npm run database:push`
https://www.prisma.io/docs/concepts/components/prisma-migrate/db-push
#### Create schema migration
Run `yarn prisma migrate dev --name added_job_title`
Run `npm run prisma migrate dev --name added_job_title`
https://www.prisma.io/docs/concepts/components/prisma-migrate#getting-started-with-prisma-migrate
## SSL
Generate `localhost.cert` and `localhost.pem` files.
```
openssl req -x509 -newkey rsa:2048 -nodes -keyout apps/client/localhost.pem -out apps/client/localhost.cert -days 365 \
-subj "/C=CH/ST=State/L=City/O=Organization/OU=Unit/CN=localhost"
```

View File

@ -1,62 +1,68 @@
FROM --platform=$BUILDPLATFORM node:18-slim as builder
FROM --platform=$BUILDPLATFORM node:22-slim AS builder
# Build application and add additional files
WORKDIR /ghostfolio
RUN apt-get update && apt-get install -y --no-install-suggests \
g++ \
git \
make \
openssl \
python3 \
&& rm -rf /var/lib/apt/lists/*
# Only add basic files without the application itself to avoid rebuilding
# layers when files (package.json etc.) have not changed
COPY ./CHANGELOG.md CHANGELOG.md
COPY ./LICENSE LICENSE
COPY ./package.json package.json
COPY ./yarn.lock yarn.lock
COPY ./.yarnrc .yarnrc
COPY ./package-lock.json package-lock.json
COPY ./prisma/schema.prisma prisma/schema.prisma
RUN apt update && apt install -y \
g++ \
git \
make \
openssl \
python3 \
&& rm -rf /var/lib/apt/lists/*
RUN yarn install
RUN npm install
# See https://github.com/nrwl/nx/issues/6586 for further details
COPY ./decorate-angular-cli.js decorate-angular-cli.js
RUN node decorate-angular-cli.js
COPY ./nx.json nx.json
COPY ./replace.build.js replace.build.js
COPY ./jest.preset.js jest.preset.js
COPY ./jest.config.ts jest.config.ts
COPY ./tsconfig.base.json tsconfig.base.json
COPY ./libs libs
COPY ./apps apps
COPY ./libs libs
COPY ./jest.config.ts jest.config.ts
COPY ./jest.preset.js jest.preset.js
COPY ./nx.json nx.json
COPY ./replace.build.mjs replace.build.mjs
COPY ./tsconfig.base.json tsconfig.base.json
RUN yarn build:production
ENV NX_DAEMON=false
RUN npm run build:production
# Prepare the dist image with additional node_modules
WORKDIR /ghostfolio/dist/apps/api
# package.json was generated by the build process, however the original
# yarn.lock needs to be used to ensure the same versions
COPY ./yarn.lock /ghostfolio/dist/apps/api/yarn.lock
# package-lock.json needs to be used to ensure the same versions
COPY ./package-lock.json /ghostfolio/dist/apps/api/package-lock.json
RUN yarn
RUN npm install
COPY prisma /ghostfolio/dist/apps/api/prisma
# Overwrite the generated package.json with the original one to ensure having
# all the scripts
# all the scripts
COPY package.json /ghostfolio/dist/apps/api
RUN yarn database:generate-typings
RUN npm run database:generate-typings
# Image to run, copy everything needed from builder
FROM node:18-slim
RUN apt update && apt install -y \
curl \
openssl \
&& rm -rf /var/lib/apt/lists/*
FROM node:22-slim
LABEL org.opencontainers.image.source="https://github.com/ghostfolio/ghostfolio"
ENV NODE_ENV=production
COPY --from=builder /ghostfolio/dist/apps /ghostfolio/apps
RUN apt-get update && apt-get install -y --no-install-suggests \
curl \
openssl \
&& rm -rf /var/lib/apt/lists/*
COPY --chown=node:node --from=builder /ghostfolio/dist/apps /ghostfolio/apps
COPY --chown=node:node ./docker/entrypoint.sh /ghostfolio/entrypoint.sh
WORKDIR /ghostfolio/apps/api
EXPOSE ${PORT:-3333}
CMD [ "yarn", "start:production" ]
USER node
CMD [ "/ghostfolio/entrypoint.sh" ]

205
README.md
View File

@ -7,13 +7,11 @@
**Open Source Wealth Management Software**
[**Ghostfol.io**](https://ghostfol.io) | [**Live Demo**](https://ghostfol.io/en/demo) | [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) | [**FAQ**](https://ghostfol.io/en/faq) |
[**Blog**](https://ghostfol.io/en/blog) | [**Slack**](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) | [**Twitter**](https://twitter.com/ghostfolio_)
[**Blog**](https://ghostfol.io/en/blog) | [**LinkedIn**](https://www.linkedin.com/company/ghostfolio) | [**Slack**](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) | [**X**](https://x.com/ghostfolio_)
[![Shield: Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-Support-yellow?logo=buymeacoffee)](https://www.buymeacoffee.com/ghostfolio)
[![Shield: Contributions Welcome](https://img.shields.io/badge/Contributions-Welcome-orange.svg)](#contributing)
[![Shield: License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
New: [Ghostfolio 2.0](https://ghostfol.io/en/blog/2023/09/ghostfolio-2)
[![Shield: Contributions Welcome](https://img.shields.io/badge/Contributions-Welcome-limegreen.svg)](#contributing) [![Shield: Docker Pulls](https://img.shields.io/docker/pulls/ghostfolio/ghostfolio?label=Docker%20Pulls)](https://hub.docker.com/r/ghostfolio/ghostfolio)
[![Shield: License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-orange.svg)](https://www.gnu.org/licenses/agpl-3.0)
</div>
@ -49,7 +47,7 @@ Ghostfolio is for you if you are...
- ✅ Create, update and delete transactions
- ✅ Multi account management
- ✅ Portfolio performance: Time-weighted rate of return (TWR) for `Today`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Portfolio performance: Return on Average Investment (ROAI) for `Today`, `WTD`, `MTD`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Various charts
- ✅ Static analysis to identify potential risks in your portfolio
- ✅ Import and export transactions
@ -73,7 +71,7 @@ The backend is based on [NestJS](https://nestjs.com) using [PostgreSQL](https://
### Frontend
The frontend is built with [Angular](https://angular.io) and uses [Angular Material](https://material.angular.io) with utility classes from [Bootstrap](https://getbootstrap.com).
The frontend is built with [Angular](https://angular.dev) and uses [Angular Material](https://material.angular.io) with utility classes from [Bootstrap](https://getbootstrap.com).
## Self-hosting
@ -87,23 +85,24 @@ We provide official container images hosted on [Docker Hub](https://hub.docker.c
### Supported Environment Variables
| Name | Default Value | Description |
| ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `ACCESS_TOKEN_SALT` | | A random string used as salt for access tokens |
| `API_KEY_COINGECKO_DEMO` |   | The _CoinGecko_ Demo API key |
| `API_KEY_COINGECKO_PRO` |   | The _CoinGecko_ Pro API |
| `DATABASE_URL` | | The database connection URL, e.g. `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer` |
| `HOST` | `0.0.0.0` | The host where the Ghostfolio application will run on |
| `JWT_SECRET_KEY` | | A random string used for _JSON Web Tokens_ (JWT) |
| `PORT` | `3333` | The port where the Ghostfolio application will run on |
| `POSTGRES_DB` | | The name of the _PostgreSQL_ database |
| `POSTGRES_PASSWORD` | | The password of the _PostgreSQL_ database |
| `POSTGRES_USER` | | The user of the _PostgreSQL_ database |
| `REDIS_DB` | `0` | The database index of _Redis_ |
| `REDIS_HOST` | | The host where _Redis_ is running |
| `REDIS_PASSWORD` | | The password of _Redis_ |
| `REDIS_PORT` | | The port where _Redis_ is running |
| `REQUEST_TIMEOUT` | `2000` | The timeout of network requests to data providers in milliseconds |
| Name | Type | Default Value | Description |
| ------------------------ | --------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `ACCESS_TOKEN_SALT` | `string` | | A random string used as salt for access tokens |
| `API_KEY_COINGECKO_DEMO` | `string` (optional) |   | The _CoinGecko_ Demo API key |
| `API_KEY_COINGECKO_PRO` | `string` (optional) | | The _CoinGecko_ Pro API key |
| `DATABASE_URL` | `string` | | The database connection URL, e.g. `postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer` |
| `HOST` | `string` (optional) | `0.0.0.0` | The host where the Ghostfolio application will run on |
| `JWT_SECRET_KEY` | `string` | | A random string used for _JSON Web Tokens_ (JWT) |
| `LOG_LEVELS` | `string[]` (optional) | | The logging levels for the Ghostfolio application, e.g. `["debug","error","log","warn"]` |
| `PORT` | `number` (optional) | `3333` | The port where the Ghostfolio application will run on |
| `POSTGRES_DB` | `string` | | The name of the _PostgreSQL_ database |
| `POSTGRES_PASSWORD` | `string` | | The password of the _PostgreSQL_ database |
| `POSTGRES_USER` | `string` | | The user of the _PostgreSQL_ database |
| `REDIS_DB` | `number` (optional) | `0` | The database index of _Redis_ |
| `REDIS_HOST` | `string` | | The host where _Redis_ is running |
| `REDIS_PASSWORD` | `string` | | The password of _Redis_ |
| `REDIS_PORT` | `number` | | The port where _Redis_ is running |
| `REQUEST_TIMEOUT` | `number` (optional) | `2000` | The timeout of network requests to data providers in milliseconds |
### Run with Docker Compose
@ -119,7 +118,7 @@ We provide official container images hosted on [Docker Hub](https://hub.docker.c
Run the following command to start the Docker images from [Docker Hub](https://hub.docker.com/r/ghostfolio/ghostfolio):
```bash
docker compose --env-file ./.env -f docker/docker-compose.yml up -d
docker compose -f docker/docker-compose.yml up -d
```
#### b. Build and run environment
@ -127,8 +126,8 @@ docker compose --env-file ./.env -f docker/docker-compose.yml up -d
Run the following commands to build and start the Docker images:
```bash
docker compose --env-file ./.env -f docker/docker-compose.build.yml build
docker compose --env-file ./.env -f docker/docker-compose.build.yml up -d
docker compose -f docker/docker-compose.build.yml build
docker compose -f docker/docker-compose.build.yml up -d
```
#### Setup
@ -138,63 +137,27 @@ docker compose --env-file ./.env -f docker/docker-compose.build.yml up -d
#### Upgrade Version
1. Increase the version of the `ghostfolio/ghostfolio` Docker image in `docker/docker-compose.yml`
1. Run the following command to start the new Docker image: `docker compose --env-file ./.env -f docker/docker-compose.yml up -d`
At each start, the container will automatically apply the database schema migrations if needed.
1. Update the _Ghostfolio_ Docker image
- Increase the version of the `ghostfolio/ghostfolio` Docker image in `docker/docker-compose.yml`
- Run the following command if `ghostfolio:latest` is set:
```bash
docker compose -f docker/docker-compose.yml pull
```
1. Run the following command to start the new Docker image:
```bash
docker compose -f docker/docker-compose.yml up -d
```
The container will automatically apply any required database schema migrations during startup.
### Home Server Systems (Community)
Ghostfolio is available for various home server systems, including [Runtipi](https://www.runtipi.io/docs/apps-available), [TrueCharts](https://truecharts.org/charts/stable/ghostfolio), [Umbrel](https://apps.umbrel.com/app/ghostfolio), and [Unraid](https://unraid.net/community/apps?q=ghostfolio).
Ghostfolio is available for various home server systems, including [CasaOS](https://github.com/bigbeartechworld/big-bear-casaos), [Home Assistant](https://github.com/lildude/ha-addon-ghostfolio), [Runtipi](https://www.runtipi.io/docs/apps-available), [TrueCharts](https://truecharts.org/charts/stable/ghostfolio), [Umbrel](https://apps.umbrel.com/app/ghostfolio), and [Unraid](https://unraid.net/community/apps?q=ghostfolio).
## Development
### Prerequisites
- [Docker](https://www.docker.com/products/docker-desktop)
- [Node.js](https://nodejs.org/en/download) (version 18+)
- [Yarn](https://yarnpkg.com/en/docs/install)
- Create a local copy of this Git repository (clone)
- Copy the file `.env.example` to `.env` and populate it with your data (`cp .env.example .env`)
### Setup
1. Run `yarn install`
1. Run `docker compose --env-file ./.env -f docker/docker-compose.dev.yml up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
1. Run `yarn database:setup` to initialize the database schema
1. Run `git config core.hooksPath ./git-hooks/` to setup git hooks
1. Start the server and the client (see [_Development_](#Development))
1. Open http://localhost:4200/en in your browser
1. Create a new user via _Get Started_ (this first user will get the role `ADMIN`)
### Start Server
#### Debug
Run `yarn watch:server` and click _Debug API_ in [Visual Studio Code](https://code.visualstudio.com)
#### Serve
Run `yarn start:server`
### Start Client
Run `yarn start:client` and open http://localhost:4200/en in your browser
### Start _Storybook_
Run `yarn start:storybook`
### Migrate Database
With the following command you can keep your database schema in sync:
```bash
yarn database:push
```
## Testing
Run `yarn test`
For detailed information on the environment setup and development process, please refer to [DEVELOPMENT.md](./DEVELOPMENT.md).
## Public API
@ -206,12 +169,36 @@ Set the header for each request as follows:
"Authorization": "Bearer eyJh..."
```
You can get the _Bearer Token_ via `POST http://localhost:3333/api/v1/auth/anonymous` (Body: `{ accessToken: <INSERT_SECURITY_TOKEN_OF_ACCOUNT> }`)
You can get the _Bearer Token_ via `POST http://localhost:3333/api/v1/auth/anonymous` (Body: `{ "accessToken": "<INSERT_SECURITY_TOKEN_OF_ACCOUNT>" }`)
Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TOKEN_OF_ACCOUNT>` or `curl -s http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TOKEN_OF_ACCOUNT>`.
### Health Check (experimental)
#### Request
`GET http://localhost:3333/api/v1/health`
**Info:** No Bearer Token is required for health check
#### Response
##### Success
`200 OK`
```
{
"status": "OK"
}
```
### Import Activities
#### Prerequisites
[Bearer Token](#authorization-bearer-token) for authorization
#### Request
`POST http://localhost:3333/api/v1/import`
@ -235,18 +222,18 @@ Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TO
}
```
| Field | Type | Description |
| ---------- | ------------------- | ----------------------------------------------------------------------------- |
| accountId | string (`optional`) | Id of the account |
| comment | string (`optional`) | Comment of the activity |
| currency | string | `CHF` \| `EUR` \| `USD` etc. |
| dataSource | string | `COINGECKO` \| `MANUAL` (for type `ITEM`) \| `YAHOO` |
| date | string | Date in the format `ISO-8601` |
| fee | number | Fee of the activity |
| quantity | number | Quantity of the activity |
| symbol | string | Symbol of the activity (suitable for `dataSource`) |
| type | string | `BUY` \| `DIVIDEND` \| `FEE` \| `INTEREST` \| `ITEM` \| `LIABILITY` \| `SELL` |
| unitPrice | number | Price per unit of the activity |
| Field | Type | Description |
| ------------ | ------------------- | ----------------------------------------------------------------------------- |
| `accountId` | `string` (optional) | Id of the account |
| `comment` | `string` (optional) | Comment of the activity |
| `currency` | `string` | `CHF` \| `EUR` \| `USD` etc. |
| `dataSource` | `string` | `COINGECKO` \| `MANUAL` (for type `ITEM`) \| `YAHOO` |
| `date` | `string` | Date in the format `ISO-8601` |
| `fee` | `number` | Fee of the activity |
| `quantity` | `number` | Quantity of the activity |
| `symbol` | `string` | Symbol of the activity (suitable for `dataSource`) |
| `type` | `string` | `BUY` \| `DIVIDEND` \| `FEE` \| `INTEREST` \| `ITEM` \| `LIABILITY` \| `SELL` |
| `unitPrice` | `number` | Price per unit of the activity |
#### Response
@ -267,6 +254,38 @@ Deprecated: `GET http://localhost:3333/api/v1/auth/anonymous/<INSERT_SECURITY_TO
}
```
### Portfolio (experimental)
#### Prerequisites
Grant access of type _Public_ in the _Access_ tab of _My Ghostfolio_.
#### Request
`GET http://localhost:3333/api/v1/public/<INSERT_ACCESS_ID>/portfolio`
**Info:** No Bearer Token is required for authorization
#### Response
##### Success
```
{
"performance": {
"1d": {
"relativeChange": 0 // normalized from -1 to 1
};
"ytd": {
"relativeChange": 0 // normalized from -1 to 1
},
"max": {
"relativeChange": 0 // normalized from -1 to 1
}
}
}
```
## Community Projects
Discover a variety of community projects for Ghostfolio: https://github.com/topics/ghostfolio
@ -277,7 +296,7 @@ Are you building your own project? Add the `ghostfolio` topic to your _GitHub_ r
Ghostfolio is **100% free** and **open source**. We encourage and support an active and healthy community that accepts contributions from the public - including you.
Not sure what to work on? We have got some ideas. Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://twitter.com/ghostfolio_) on _X_. We would love to hear from you.
Not sure what to work on? We have [some ideas](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22), even for [newcomers](https://github.com/ghostfolio/ghostfolio/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). Please join the Ghostfolio [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) channel or post to [@ghostfolio\_](https://x.com/ghostfolio_) on _X_. We would love to hear from you.
If you like to support this project, get [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) or [**Buy me a coffee**](https://www.buymeacoffee.com/ghostfolio).
@ -287,6 +306,6 @@ If you like to support this project, get [**Ghostfolio Premium**](https://ghostf
## License
© 2021 - 2024 [Ghostfolio](https://ghostfol.io)
© 2021 - 2025 [Ghostfolio](https://ghostfol.io)
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).

13
SECURITY.md Normal file
View File

@ -0,0 +1,13 @@
# Security Policy
## Reporting Security Issues
If you discover a security vulnerability in this repository, please report it to security[at]ghostfol.io. We will acknowledge your report and provide guidance on the next steps.
To help us resolve the issue, please include the following details:
- A description of the vulnerability
- Steps to reproduce the vulnerability
- Affected versions of the software
We appreciate your responsible disclosure and will work to address the issue promptly.

View File

@ -1,22 +0,0 @@
{
"extends": "../../.eslintrc.json",
"ignorePatterns": ["!**/*"],
"rules": {},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"parserOptions": {
"project": ["apps/api/tsconfig.*?.json"]
},
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,31 @@
const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist']
},
...baseConfig,
{
rules: {}
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
languageOptions: {
parserOptions: {
project: ['apps/api/tsconfig.*?.json']
}
}
},
{
files: ['**/*.ts', '**/*.tsx'],
// Override or add rules here
rules: {}
},
{
files: ['**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {}
}
];

View File

@ -13,7 +13,6 @@ export default {
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/api',
testTimeout: 10000,
testEnvironment: 'node',
preset: '../../jest.preset.js'
};

View File

@ -37,20 +37,20 @@ export class AccessController {
public async getAllAccesses(): Promise<Access[]> {
const accessesWithGranteeUser = await this.accessService.accesses({
include: {
GranteeUser: true
granteeUser: true
},
orderBy: { granteeUserId: 'asc' },
where: { userId: this.request.user.id }
});
return accessesWithGranteeUser.map(
({ alias, GranteeUser, id, permissions }) => {
if (GranteeUser) {
({ alias, granteeUser, id, permissions }) => {
if (granteeUser) {
return {
alias,
id,
permissions,
grantee: GranteeUser?.id,
grantee: granteeUser?.id,
type: 'PRIVATE'
};
}
@ -83,13 +83,13 @@ export class AccessController {
}
try {
return await this.accessService.createAccess({
return this.accessService.createAccess({
alias: data.alias || undefined,
GranteeUser: data.granteeUserId
granteeUser: data.granteeUserId
? { connect: { id: data.granteeUserId } }
: undefined,
permissions: data.permissions,
User: { connect: { id: this.request.user.id } }
user: { connect: { id: this.request.user.id } }
});
} catch {
throw new HttpException(

View File

@ -13,7 +13,7 @@ export class AccessService {
): Promise<AccessWithGranteeUser | null> {
return this.prismaService.access.findFirst({
include: {
GranteeUser: true
granteeUser: true
},
where: accessWhereInput
});

View File

@ -1,3 +1,4 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { permissions } from '@ghostfolio/common/permissions';
@ -5,6 +6,8 @@ import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Body,
Post,
Delete,
HttpException,
Inject,
@ -17,14 +20,44 @@ import { AccountBalance } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccountBalanceService } from './account-balance.service';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
@Controller('account-balance')
export class AccountBalanceController {
public constructor(
private readonly accountBalanceService: AccountBalanceService,
private readonly accountService: AccountService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@HasPermission(permissions.createAccountBalance)
@Post()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createAccountBalance(
@Body() data: CreateAccountBalanceDto
): Promise<AccountBalance> {
const account = await this.accountService.account({
id_userId: {
id: data.accountId,
userId: this.request.user.id
}
});
if (!account) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.accountBalanceService.createOrUpdateAccountBalance({
accountId: account.id,
balance: data.balance,
date: data.date,
userId: account.userId
});
}
@HasPermission(permissions.deleteAccountBalance)
@Delete(':id')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@ -32,10 +65,11 @@ export class AccountBalanceController {
@Param('id') id: string
): Promise<AccountBalance> {
const accountBalance = await this.accountBalanceService.accountBalance({
id
id,
userId: this.request.user.id
});
if (!accountBalance || accountBalance.userId !== this.request.user.id) {
if (!accountBalance) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
@ -43,7 +77,8 @@ export class AccountBalanceController {
}
return this.accountBalanceService.deleteAccountBalance({
id
id: accountBalance.id,
userId: accountBalance.userId
});
}
}

View File

@ -1,3 +1,4 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
@ -10,6 +11,6 @@ import { AccountBalanceService } from './account-balance.service';
controllers: [AccountBalanceController],
exports: [AccountBalanceService],
imports: [ExchangeRateDataModule, PrismaModule],
providers: [AccountBalanceService]
providers: [AccountBalanceService, AccountService]
})
export class AccountBalanceModule {}

View File

@ -1,14 +1,26 @@
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { LogPerformance } from '@ghostfolio/api/interceptors/performance-logging/performance-logging.interceptor';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { AccountBalancesResponse, Filter } from '@ghostfolio/common/interfaces';
import { UserWithSettings } from '@ghostfolio/common/types';
import { DATE_FORMAT, getSum, resetHours } from '@ghostfolio/common/helper';
import {
AccountBalancesResponse,
Filter,
HistoricalDataItem
} from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { AccountBalance, Prisma } from '@prisma/client';
import { Big } from 'big.js';
import { format, parseISO } from 'date-fns';
import { CreateAccountBalanceDto } from './create-account-balance.dto';
@Injectable()
export class AccountBalanceService {
public constructor(
private readonly eventEmitter: EventEmitter2,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly prismaService: PrismaService
) {}
@ -18,38 +30,120 @@ export class AccountBalanceService {
): Promise<AccountBalance | null> {
return this.prismaService.accountBalance.findFirst({
include: {
Account: true
account: true
},
where: accountBalanceWhereInput
});
}
public async createAccountBalance(
data: Prisma.AccountBalanceCreateInput
): Promise<AccountBalance> {
return this.prismaService.accountBalance.create({
data
public async createOrUpdateAccountBalance({
accountId,
balance,
date,
userId
}: CreateAccountBalanceDto & {
userId: string;
}): Promise<AccountBalance> {
const accountBalance = await this.prismaService.accountBalance.upsert({
create: {
account: {
connect: {
id_userId: {
userId,
id: accountId
}
}
},
date: resetHours(parseISO(date)),
value: balance
},
update: {
value: balance
},
where: {
accountId_date: {
accountId,
date: resetHours(parseISO(date))
}
}
});
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId
})
);
return accountBalance;
}
public async deleteAccountBalance(
where: Prisma.AccountBalanceWhereUniqueInput
): Promise<AccountBalance> {
return this.prismaService.accountBalance.delete({
const accountBalance = await this.prismaService.accountBalance.delete({
where
});
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId: where.userId as string
})
);
return accountBalance;
}
public async getAccountBalanceItems({
filters,
userCurrency,
userId
}: {
filters?: Filter[];
userCurrency: string;
userId: string;
}): Promise<HistoricalDataItem[]> {
const { balances } = await this.getAccountBalances({
filters,
userCurrency,
userId,
withExcludedAccounts: false // TODO
});
const accumulatedBalancesByDate: { [date: string]: HistoricalDataItem } =
{};
const lastBalancesByAccount: { [accountId: string]: Big } = {};
for (const { accountId, date, valueInBaseCurrency } of balances) {
const formattedDate = format(date, DATE_FORMAT);
lastBalancesByAccount[accountId] = new Big(valueInBaseCurrency);
const totalBalance = getSum(Object.values(lastBalancesByAccount));
// Add or update the accumulated balance for this date
accumulatedBalancesByDate[formattedDate] = {
date: formattedDate,
value: totalBalance.toNumber()
};
}
return Object.values(accumulatedBalancesByDate);
}
@LogPerformance
public async getAccountBalances({
filters,
user,
userCurrency,
userId,
withExcludedAccounts
}: {
filters?: Filter[];
user: UserWithSettings;
userCurrency: string;
userId: string;
withExcludedAccounts?: boolean;
}): Promise<AccountBalancesResponse> {
const where: Prisma.AccountBalanceWhereInput = { userId: user.id };
const where: Prisma.AccountBalanceWhereInput = { userId };
const accountFilter = filters?.find(({ type }) => {
return type === 'ACCOUNT';
@ -60,7 +154,7 @@ export class AccountBalanceService {
}
if (withExcludedAccounts === false) {
where.Account = { isExcluded: false };
where.account = { isExcluded: false };
}
const balances = await this.prismaService.accountBalance.findMany({
@ -69,7 +163,7 @@ export class AccountBalanceService {
date: 'asc'
},
select: {
Account: true,
account: true,
date: true,
id: true,
value: true
@ -80,10 +174,11 @@ export class AccountBalanceService {
balances: balances.map((balance) => {
return {
...balance,
accountId: balance.account.id,
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
balance.value,
balance.Account.currency,
user.Settings.settings.baseCurrency
balance.account.currency,
userCurrency
)
};
})

View File

@ -0,0 +1,12 @@
import { IsISO8601, IsNumber, IsUUID } from 'class-validator';
export class CreateAccountBalanceDto {
@IsUUID()
accountId: string;
@IsNumber()
balance: number;
@IsISO8601()
date: string;
}

View File

@ -2,12 +2,14 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/accou
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import {
AccountBalancesResponse,
Accounts
AccountsResponse
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type {
@ -26,6 +28,7 @@ import {
Param,
Post,
Put,
Query,
UseGuards,
UseInterceptors
} from '@nestjs/common';
@ -44,6 +47,7 @@ export class AccountController {
public constructor(
private readonly accountBalanceService: AccountBalanceService,
private readonly accountService: AccountService,
private readonly apiService: ApiService,
private readonly impersonationService: ImpersonationService,
private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser
@ -53,44 +57,50 @@ export class AccountController {
@HasPermission(permissions.deleteAccount)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteAccount(@Param('id') id: string): Promise<AccountModel> {
const account = await this.accountService.accountWithOrders(
const account = await this.accountService.accountWithActivities(
{
id_userId: {
id,
userId: this.request.user.id
}
},
{ Order: true }
{ activities: true }
);
if (!account || account?.Order.length > 0) {
if (!account || account?.activities.length > 0) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.accountService.deleteAccount(
{
id_userId: {
id,
userId: this.request.user.id
}
},
this.request.user.id
);
return this.accountService.deleteAccount({
id_userId: {
id,
userId: this.request.user.id
}
});
}
@Get()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getAllAccounts(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId
): Promise<Accounts> {
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string
): Promise<AccountsResponse> {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId);
const filters = this.apiService.buildFiltersFromQueryParams({
filterByDataSource,
filterBySymbol
});
return this.portfolioService.getAccountsWithAggregations({
filters,
userId: impersonationUserId || this.request.user.id,
withExcludedAccounts: true
});
@ -100,7 +110,7 @@ export class AccountController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
public async getAccountById(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Param('id') id: string
): Promise<AccountWithValue> {
const impersonationUserId =
@ -124,7 +134,8 @@ export class AccountController {
): Promise<AccountBalancesResponse> {
return this.accountBalanceService.getAccountBalances({
filters: [{ id, type: 'ACCOUNT' }],
user: this.request.user
userCurrency: this.request.user.Settings.settings.baseCurrency,
userId: this.request.user.id
});
}
@ -141,8 +152,8 @@ export class AccountController {
return this.accountService.createAccount(
{
...data,
Platform: { connect: { id: platformId } },
User: { connect: { id: this.request.user.id } }
platform: { connect: { id: platformId } },
user: { connect: { id: this.request.user.id } }
},
this.request.user.id
);
@ -152,7 +163,7 @@ export class AccountController {
return this.accountService.createAccount(
{
...data,
User: { connect: { id: this.request.user.id } }
user: { connect: { id: this.request.user.id } }
},
this.request.user.id
);
@ -239,8 +250,8 @@ export class AccountController {
{
data: {
...data,
Platform: { connect: { id: platformId } },
User: { connect: { id: this.request.user.id } }
platform: { connect: { id: platformId } },
user: { connect: { id: this.request.user.id } }
},
where: {
id_userId: {
@ -259,10 +270,10 @@ export class AccountController {
{
data: {
...data,
Platform: originalAccount.platformId
platform: originalAccount.platformId
? { disconnect: true }
: undefined,
User: { connect: { id: this.request.user.id } }
user: { connect: { id: this.request.user.id } }
},
where: {
id_userId: {

View File

@ -1,9 +1,8 @@
import { AccountBalanceModule } from '@ghostfolio/api/app/account-balance/account-balance.module';
import { PortfolioModule } from '@ghostfolio/api/app/portfolio/portfolio.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.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 { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
@ -18,14 +17,13 @@ import { AccountService } from './account.service';
exports: [AccountService],
imports: [
AccountBalanceModule,
ApiModule,
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
ImpersonationModule,
PortfolioModule,
PrismaModule,
RedisCacheModule,
UserModule
RedactValuesInResponseModule
],
providers: [AccountService]
})

View File

@ -1,11 +1,21 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { DATE_FORMAT } from '@ghostfolio/common/helper';
import { Filter } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { Account, Order, Platform, Prisma } from '@prisma/client';
import Big from 'big.js';
import { EventEmitter2 } from '@nestjs/event-emitter';
import {
Account,
AccountBalance,
Order,
Platform,
Prisma
} from '@prisma/client';
import { Big } from 'big.js';
import { format } from 'date-fns';
import { groupBy } from 'lodash';
import { CashDetails } from './interfaces/cash-details.interface';
@ -14,6 +24,7 @@ import { CashDetails } from './interfaces/cash-details.interface';
export class AccountService {
public constructor(
private readonly accountBalanceService: AccountBalanceService,
private readonly eventEmitter: EventEmitter2,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly prismaService: PrismaService
) {}
@ -28,12 +39,12 @@ export class AccountService {
return account;
}
public async accountWithOrders(
public async accountWithActivities(
accountWhereUniqueInput: Prisma.AccountWhereUniqueInput,
accountInclude: Prisma.AccountInclude
): Promise<
Account & {
Order?: Order[];
activities?: Order[];
}
> {
return this.prismaService.account.findUnique({
@ -51,13 +62,19 @@ export class AccountService {
orderBy?: Prisma.AccountOrderByWithRelationInput;
}): Promise<
(Account & {
Order?: Order[];
Platform?: Platform;
activities?: Order[];
balances?: AccountBalance[];
platform?: Platform;
})[]
> {
const { include = {}, skip, take, cursor, where, orderBy } = params;
include.balances = { orderBy: { date: 'desc' }, take: 1 };
const isBalancesIncluded = !!include.balances;
include.balances = {
orderBy: { date: 'desc' },
...(isBalancesIncluded ? {} : { take: 1 })
};
const accounts = await this.prismaService.account.findMany({
cursor,
@ -71,7 +88,9 @@ export class AccountService {
return accounts.map((account) => {
account = { ...account, balance: account.balances[0]?.value ?? 0 };
delete account.balances;
if (!isBalancesIncluded) {
delete account.balances;
}
return account;
});
@ -85,32 +104,46 @@ export class AccountService {
data
});
await this.prismaService.accountBalance.create({
data: {
Account: {
connect: {
id_userId: { id: account.id, userId: aUserId }
}
},
value: data.balance
}
await this.accountBalanceService.createOrUpdateAccountBalance({
accountId: account.id,
balance: data.balance,
date: format(new Date(), DATE_FORMAT),
userId: aUserId
});
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId: account.userId
})
);
return account;
}
public async deleteAccount(
where: Prisma.AccountWhereUniqueInput,
aUserId: string
where: Prisma.AccountWhereUniqueInput
): Promise<Account> {
return this.prismaService.account.delete({
const account = await this.prismaService.account.delete({
where
});
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId: account.userId
})
);
return account;
}
public async getAccounts(aUserId: string): Promise<Account[]> {
const accounts = await this.accounts({
include: { Order: true, Platform: true },
include: {
activities: true,
platform: true
},
orderBy: { name: 'asc' },
where: { userId: aUserId }
});
@ -118,15 +151,15 @@ export class AccountService {
return accounts.map((account) => {
let transactionCount = 0;
for (const order of account.Order) {
if (!order.isDraft) {
for (const { isDraft } of account.activities) {
if (!isDraft) {
transactionCount += 1;
}
}
const result = { ...account, transactionCount };
delete result.Order;
delete result.activities;
return result;
});
@ -153,12 +186,8 @@ export class AccountService {
where.isExcluded = false;
}
const {
ACCOUNT: filtersByAccount,
ASSET_CLASS: filtersByAssetClass,
TAG: filtersByTag
} = groupBy(filters, (filter) => {
return filter.type;
const { ACCOUNT: filtersByAccount } = groupBy(filters, ({ type }) => {
return type;
});
if (filtersByAccount?.length > 0) {
@ -196,21 +225,26 @@ export class AccountService {
): Promise<Account> {
const { data, where } = params;
await this.prismaService.accountBalance.create({
data: {
Account: {
connect: {
id_userId: where.id_userId
}
},
value: <number>data.balance
}
await this.accountBalanceService.createOrUpdateAccountBalance({
accountId: data.id as string,
balance: data.balance as number,
date: format(new Date(), DATE_FORMAT),
userId: aUserId
});
return this.prismaService.account.update({
const account = await this.prismaService.account.update({
data,
where
});
this.eventEmitter.emit(
PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({
userId: account.userId
})
);
return account;
}
public async updateAccountBalance({
@ -242,17 +276,11 @@ export class AccountService {
);
if (amountInCurrencyOfAccount) {
await this.accountBalanceService.createAccountBalance({
date,
Account: {
connect: {
id_userId: {
userId,
id: accountId
}
}
},
value: new Big(balance).plus(amountInCurrencyOfAccount).toNumber()
await this.accountBalanceService.createOrUpdateAccountBalance({
accountId,
userId,
balance: new Big(balance).plus(amountInCurrencyOfAccount).toNumber(),
date: date.toISOString()
});
}
}

View File

@ -1,7 +1,8 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { Transform, TransformFnParams } from 'class-transformer';
import {
IsBoolean,
IsISO4217CurrencyCode,
IsNumber,
IsOptional,
IsString,
@ -20,7 +21,7 @@ export class CreateAccountDto {
)
comment?: string;
@IsISO4217CurrencyCode()
@IsCurrencyCode()
currency: string;
@IsOptional()
@ -35,6 +36,6 @@ export class CreateAccountDto {
name: string;
@IsString()
@ValidateIf((object, value) => value !== null)
@ValidateIf((_object, value) => value !== null)
platformId: string | null;
}

View File

@ -1,7 +1,8 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { Transform, TransformFnParams } from 'class-transformer';
import {
IsBoolean,
IsISO4217CurrencyCode,
IsNumber,
IsOptional,
IsString,
@ -20,7 +21,7 @@ export class UpdateAccountDto {
)
comment?: string;
@IsISO4217CurrencyCode()
@IsCurrencyCode()
currency: string;
@IsString()
@ -34,6 +35,6 @@ export class UpdateAccountDto {
name: string;
@IsString()
@ValidateIf((object, value) => value !== null)
@ValidateIf((_object, value) => value !== null)
platformId: string | null;
}

View File

@ -1,24 +1,24 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { DemoService } from '@ghostfolio/api/services/demo/demo.service';
import { PropertyDto } from '@ghostfolio/api/services/property/property.dto';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import {
GATHER_ASSET_PROFILE_PROCESS,
GATHER_ASSET_PROFILE_PROCESS_OPTIONS
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM,
GATHER_ASSET_PROFILE_PROCESS_JOB_NAME,
GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS
} from '@ghostfolio/common/config';
import {
getAssetProfileIdentifier,
resetHours
} from '@ghostfolio/common/helper';
import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import {
AdminData,
AdminMarketData,
AdminMarketDataDetails,
EnhancedSymbolProfile
AdminUsers,
EnhancedSymbolProfile,
ScraperConfiguration
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type {
@ -50,8 +50,6 @@ import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AdminService } from './admin.service';
import { UpdateAssetProfileDto } from './update-asset-profile.dto';
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto';
import { UpdateMarketDataDto } from './update-market-data.dto';
@Controller('admin')
export class AdminController {
@ -59,8 +57,8 @@ export class AdminController {
private readonly adminService: AdminService,
private readonly apiService: ApiService,
private readonly dataGatheringService: DataGatheringService,
private readonly demoService: DemoService,
private readonly manualService: ManualService,
private readonly marketDataService: MarketDataService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@ -68,7 +66,14 @@ export class AdminController {
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getAdminData(): Promise<AdminData> {
return this.adminService.get();
return this.adminService.get({ user: this.request.user });
}
@Get('demo-user/sync')
@HasPermission(permissions.syncDemoUserAccount)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async syncDemoUserAccount(): Promise<Prisma.BatchPayload> {
return this.demoService.syncDemoUserAccount();
}
@HasPermission(permissions.accessAdminControl)
@ -82,19 +87,21 @@ export class AdminController {
@Post('gather/max')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gatherMax(): Promise<void> {
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
const assetProfileIdentifiers =
await this.dataGatheringService.getAllActiveAssetProfileIdentifiers();
await this.dataGatheringService.addJobsToQueue(
uniqueAssets.map(({ dataSource, symbol }) => {
assetProfileIdentifiers.map(({ dataSource, symbol }) => {
return {
data: {
dataSource,
symbol
},
name: GATHER_ASSET_PROFILE_PROCESS,
name: GATHER_ASSET_PROFILE_PROCESS_JOB_NAME,
opts: {
...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol })
...GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol }),
priority: DATA_GATHERING_QUEUE_PRIORITY_MEDIUM
}
};
})
@ -107,19 +114,21 @@ export class AdminController {
@Post('gather/profile-data')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gatherProfileData(): Promise<void> {
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
const assetProfileIdentifiers =
await this.dataGatheringService.getAllActiveAssetProfileIdentifiers();
await this.dataGatheringService.addJobsToQueue(
uniqueAssets.map(({ dataSource, symbol }) => {
assetProfileIdentifiers.map(({ dataSource, symbol }) => {
return {
data: {
dataSource,
symbol
},
name: GATHER_ASSET_PROFILE_PROCESS,
name: GATHER_ASSET_PROFILE_PROCESS_JOB_NAME,
opts: {
...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol })
...GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol }),
priority: DATA_GATHERING_QUEUE_PRIORITY_MEDIUM
}
};
})
@ -138,10 +147,11 @@ export class AdminController {
dataSource,
symbol
},
name: GATHER_ASSET_PROFILE_PROCESS,
name: GATHER_ASSET_PROFILE_PROCESS_JOB_NAME,
opts: {
...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol })
...GATHER_ASSET_PROFILE_PROCESS_JOB_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol }),
priority: DATA_GATHERING_QUEUE_PRIORITY_HIGH
}
});
}
@ -209,89 +219,31 @@ export class AdminController {
});
}
@Get('market-data/:dataSource/:symbol')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<AdminMarketDataDetails> {
return this.adminService.getMarketDataBySymbol({ dataSource, symbol });
}
@HasPermission(permissions.accessAdminControl)
@Post('market-data/:dataSource/:symbol/test')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async testMarketData(
@Body() data: { scraperConfiguration: string },
@Body() data: { scraperConfiguration: ScraperConfiguration },
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<{ price: number }> {
try {
const scraperConfiguration = JSON.parse(data.scraperConfiguration);
const price = await this.manualService.test(scraperConfiguration);
const price = await this.manualService.test(data.scraperConfiguration);
if (price) {
return { price };
}
throw new Error('Could not parse the current market price');
throw new Error(
`Could not parse the current market price for ${symbol} (${dataSource})`
);
} catch (error) {
Logger.error(error);
Logger.error(error, 'AdminController');
throw new HttpException(error.message, StatusCodes.BAD_REQUEST);
}
}
@HasPermission(permissions.accessAdminControl)
@Post('market-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updateMarketData(
@Body() data: UpdateBulkMarketDataDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map(
({ date, marketPrice }) => ({
dataSource,
marketPrice,
symbol,
date: parseISO(date),
state: 'CLOSE'
})
);
return this.marketDataService.updateMany({
data: dataBulkUpdate
});
}
/**
* @deprecated
*/
@HasPermission(permissions.accessAdminControl)
@Put('market-data/:dataSource/:symbol/:dateString')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async update(
@Param('dataSource') dataSource: DataSource,
@Param('dateString') dateString: string,
@Param('symbol') symbol: string,
@Body() data: UpdateMarketDataDto
) {
const date = parseISO(dateString);
return this.marketDataService.updateMarketData({
data: { marketPrice: data.marketPrice, state: 'CLOSE' },
where: {
dataSource_date_symbol: {
dataSource,
date,
symbol
}
}
});
}
@HasPermission(permissions.accessAdminControl)
@Post('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@ -321,15 +273,14 @@ export class AdminController {
@Patch('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async patchAssetProfileData(
@Body() assetProfileData: UpdateAssetProfileDto,
@Body() assetProfile: UpdateAssetProfileDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<EnhancedSymbolProfile> {
return this.adminService.patchAssetProfileData({
...assetProfileData,
dataSource,
symbol
});
return this.adminService.patchAssetProfileData(
{ dataSource, symbol },
assetProfile
);
}
@HasPermission(permissions.accessAdminControl)
@ -339,6 +290,19 @@ export class AdminController {
@Param('key') key: string,
@Body() data: PropertyDto
) {
return await this.adminService.putSetting(key, data.value);
return this.adminService.putSetting(key, data.value);
}
@Get('user')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getUsers(
@Query('skip') skip?: number,
@Query('take') take?: number
): Promise<AdminUsers> {
return this.adminService.getUsers({
skip: isNaN(skip) ? undefined : skip,
take: isNaN(take) ? undefined : take
});
}
}

View File

@ -1,12 +1,15 @@
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { DemoModule } from '@ghostfolio/api/services/demo/demo.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 { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
@ -18,16 +21,19 @@ import { QueueModule } from './queue/queue.module';
@Module({
imports: [
ApiModule,
BenchmarkModule,
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
DemoModule,
ExchangeRateDataModule,
MarketDataModule,
OrderModule,
PrismaModule,
PropertyModule,
QueueModule,
SubscriptionModule,
SymbolProfileModule
SymbolProfileModule,
TransformDataSourceInRequestModule
],
controllers: [AdminController],
providers: [AdminService],

View File

@ -1,5 +1,6 @@
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { environment } from '@ghostfolio/api/environments/environment';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
@ -8,42 +9,58 @@ import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import {
DEFAULT_CURRENCY,
PROPERTY_CURRENCIES,
PROPERTY_IS_READ_ONLY_MODE,
PROPERTY_IS_USER_SIGNUP_ENABLED
} from '@ghostfolio/common/config';
import {
getAssetProfileIdentifier,
getCurrencyFromSymbol,
isCurrency
} from '@ghostfolio/common/helper';
import {
AdminData,
AdminMarketData,
AdminMarketDataDetails,
AdminMarketDataItem,
Filter,
UniqueAsset
AdminUsers,
AssetProfileIdentifier,
EnhancedSymbolProfile,
Filter
} from '@ghostfolio/common/interfaces';
import { MarketDataPreset } from '@ghostfolio/common/types';
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { MarketDataPreset, UserWithSettings } from '@ghostfolio/common/types';
import { BadRequestException, Injectable } from '@nestjs/common';
import {
BadRequestException,
HttpException,
Injectable,
Logger
} from '@nestjs/common';
import {
AssetClass,
AssetSubClass,
DataSource,
Prisma,
PrismaClient,
Property,
SymbolProfile
} from '@prisma/client';
import { differenceInDays } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { groupBy } from 'lodash';
@Injectable()
export class AdminService {
public constructor(
private readonly benchmarkService: BenchmarkService,
private readonly configurationService: ConfigurationService,
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly marketDataService: MarketDataService,
private readonly orderService: OrderService,
private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService,
private readonly subscriptionService: SubscriptionService,
private readonly symbolProfileService: SymbolProfileService
) {}
@ -51,7 +68,9 @@ export class AdminService {
currency,
dataSource,
symbol
}: UniqueAsset & { currency?: string }): Promise<SymbolProfile | never> {
}: AssetProfileIdentifier & { currency?: string }): Promise<
SymbolProfile | never
> {
try {
if (dataSource === 'MANUAL') {
return this.symbolProfileService.add({
@ -71,7 +90,7 @@ export class AdminService {
);
}
return await this.symbolProfileService.add(
return this.symbolProfileService.add(
assetProfiles[symbol] as Prisma.SymbolProfileCreateInput
);
} catch (error) {
@ -88,41 +107,68 @@ export class AdminService {
}
}
public async deleteProfileData({ dataSource, symbol }: UniqueAsset) {
public async deleteProfileData({
dataSource,
symbol
}: AssetProfileIdentifier) {
await this.marketDataService.deleteMany({ dataSource, symbol });
await this.symbolProfileService.delete({ dataSource, symbol });
const currency = getCurrencyFromSymbol(symbol);
const customCurrencies =
await this.propertyService.getByKey<string[]>(PROPERTY_CURRENCIES);
if (customCurrencies.includes(currency)) {
const updatedCustomCurrencies = customCurrencies.filter(
(customCurrency) => {
return customCurrency !== currency;
}
);
await this.putSetting(
PROPERTY_CURRENCIES,
JSON.stringify(updatedCustomCurrencies)
);
} else {
await this.symbolProfileService.delete({ dataSource, symbol });
}
}
public async get(): Promise<AdminData> {
return {
exchangeRates: this.exchangeRateDataService
.getCurrencies()
.filter((currency) => {
return currency !== DEFAULT_CURRENCY;
})
.map((currency) => {
const label1 = DEFAULT_CURRENCY;
const label2 = currency;
public async get({ user }: { user: UserWithSettings }): Promise<AdminData> {
const dataSources = await this.dataProviderService.getDataSources({
user,
includeGhostfolio: true
});
return {
label1,
label2,
dataSource:
DataSource[
this.configurationService.get('DATA_SOURCE_EXCHANGE_RATES')
],
symbol: `${label1}${label2}`,
value: this.exchangeRateDataService.toCurrency(
1,
DEFAULT_CURRENCY,
currency
)
};
}),
settings: await this.propertyService.get(),
transactionCount: await this.prismaService.order.count(),
userCount: await this.prismaService.user.count(),
users: await this.getUsersWithAnalytics(),
const [settings, transactionCount, userCount] = await Promise.all([
this.propertyService.get(),
this.prismaService.order.count(),
this.countUsersWithAnalytics()
]);
const dataProviders = await Promise.all(
dataSources.map(async (dataSource) => {
const dataProviderInfo = this.dataProviderService
.getDataProvider(dataSource)
.getDataProviderInfo();
const assetProfileCount = await this.prismaService.symbolProfile.count({
where: {
dataSource
}
});
return {
...dataProviderInfo,
assetProfileCount
};
})
);
return {
dataProviders,
settings,
transactionCount,
userCount,
version: environment.version
};
}
@ -146,7 +192,16 @@ export class AdminService {
[{ symbol: 'asc' }];
const where: Prisma.SymbolProfileWhereInput = {};
if (presetId === 'CURRENCIES') {
if (presetId === 'BENCHMARKS') {
const benchmarkAssetProfiles =
await this.benchmarkService.getBenchmarkAssetProfiles();
where.id = {
in: benchmarkAssetProfiles.map(({ id }) => {
return id;
})
};
} else if (presetId === 'CURRENCIES') {
return this.getMarketDataForCurrencies();
} else if (
presetId === 'ETF_WITHOUT_COUNTRIES' ||
@ -189,108 +244,211 @@ export class AdminService {
if (sortColumn === 'activitiesCount') {
orderBy = {
Order: {
activities: {
_count: sortDirection
}
};
}
}
let [assetProfiles, count] = await Promise.all([
this.prismaService.symbolProfile.findMany({
orderBy,
skip,
take,
where,
const extendedPrismaClient = this.getExtendedPrismaClient();
try {
const symbolProfileResult = await Promise.all([
extendedPrismaClient.symbolProfile.findMany({
orderBy,
skip,
take,
where,
select: {
_count: {
select: {
activities: true,
watchedBy: true
}
},
activities: {
orderBy: [{ date: 'asc' }],
select: { date: true },
take: 1
},
assetClass: true,
assetSubClass: true,
comment: true,
countries: true,
currency: true,
dataSource: true,
id: true,
isActive: true,
isUsedByUsersWithSubscription: true,
name: true,
scraperConfiguration: true,
sectors: true,
symbol: true,
SymbolProfileOverrides: true
}
}),
this.prismaService.symbolProfile.count({ where })
]);
const assetProfiles = symbolProfileResult[0];
let count = symbolProfileResult[1];
const lastMarketPrices = await this.prismaService.marketData.findMany({
distinct: ['dataSource', 'symbol'],
orderBy: { date: 'desc' },
select: {
_count: {
select: { Order: true }
},
assetClass: true,
assetSubClass: true,
comment: true,
countries: true,
currency: true,
dataSource: true,
name: true,
Order: {
orderBy: [{ date: 'asc' }],
select: { date: true },
take: 1
},
scraperConfiguration: true,
sectors: true,
marketPrice: true,
symbol: true
},
where: {
dataSource: {
in: assetProfiles.map(({ dataSource }) => {
return dataSource;
})
},
symbol: {
in: assetProfiles.map(({ symbol }) => {
return symbol;
})
}
}
}),
this.prismaService.symbolProfile.count({ where })
]);
});
let marketData: AdminMarketDataItem[] = assetProfiles.map(
({
_count,
assetClass,
assetSubClass,
comment,
countries,
currency,
dataSource,
name,
Order,
sectors,
symbol
}) => {
const countriesCount = countries ? Object.keys(countries).length : 0;
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
const lastMarketPriceMap = new Map<string, number>();
for (const { dataSource, marketPrice, symbol } of lastMarketPrices) {
lastMarketPriceMap.set(
getAssetProfileIdentifier({ dataSource, symbol }),
marketPrice
);
}
let marketData: AdminMarketDataItem[] = await Promise.all(
assetProfiles.map(
async ({
_count,
activities,
assetClass,
assetSubClass,
comment,
countries,
currency,
dataSource,
id,
isActive,
isUsedByUsersWithSubscription,
name,
sectors,
symbol,
SymbolProfileOverrides
}) => {
let countriesCount = countries ? Object.keys(countries).length : 0;
const lastMarketPrice = lastMarketPriceMap.get(
getAssetProfileIdentifier({ dataSource, symbol })
);
})?._count ?? 0;
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
return {
assetClass,
assetSubClass,
comment,
currency,
countriesCount,
dataSource,
name,
symbol,
marketDataItemCount,
sectorsCount,
activitiesCount: _count.Order,
date: Order?.[0]?.date
};
}
);
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
);
})?._count ?? 0;
if (presetId) {
if (presetId === 'ETF_WITHOUT_COUNTRIES') {
marketData = marketData.filter(({ countriesCount }) => {
return countriesCount === 0;
});
} else if (presetId === 'ETF_WITHOUT_SECTORS') {
marketData = marketData.filter(({ sectorsCount }) => {
return sectorsCount === 0;
});
let sectorsCount = sectors ? Object.keys(sectors).length : 0;
if (SymbolProfileOverrides) {
assetClass = SymbolProfileOverrides.assetClass ?? assetClass;
assetSubClass =
SymbolProfileOverrides.assetSubClass ?? assetSubClass;
if (
(
SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
)?.length > 0
) {
countriesCount = (
SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
).length;
}
name = SymbolProfileOverrides.name ?? name;
if (
(SymbolProfileOverrides.sectors as unknown as Sector[])
?.length > 0
) {
sectorsCount = (
SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray
).length;
}
}
return {
assetClass,
assetSubClass,
comment,
currency,
countriesCount,
dataSource,
id,
isActive,
lastMarketPrice,
name,
symbol,
marketDataItemCount,
sectorsCount,
activitiesCount: _count.activities,
date: activities?.[0]?.date,
isUsedByUsersWithSubscription:
await isUsedByUsersWithSubscription,
watchedByCount: _count.watchedBy
};
}
)
);
if (presetId) {
if (presetId === 'ETF_WITHOUT_COUNTRIES') {
marketData = marketData.filter(({ countriesCount }) => {
return countriesCount === 0;
});
} else if (presetId === 'ETF_WITHOUT_SECTORS') {
marketData = marketData.filter(({ sectorsCount }) => {
return sectorsCount === 0;
});
}
count = marketData.length;
}
count = marketData.length;
return {
count,
marketData
};
} finally {
await extendedPrismaClient.$disconnect();
Logger.debug('Disconnect extended prisma client', 'AdminService');
}
return {
count,
marketData
};
}
public async getMarketDataBySymbol({
dataSource,
symbol
}: UniqueAsset): Promise<AdminMarketDataDetails> {
}: AssetProfileIdentifier): Promise<AdminMarketDataDetails> {
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
if (isCurrency(getCurrencyFromSymbol(symbol))) {
currency = getCurrencyFromSymbol(symbol);
({ activitiesCount, dateOfFirstActivity } =
await this.orderService.getStatisticsByCurrency(currency));
}
const [[assetProfile], marketData] = await Promise.all([
this.symbolProfileService.getSymbolProfiles([
{
@ -309,50 +467,160 @@ export class AdminService {
})
]);
if (assetProfile) {
assetProfile.dataProviderInfo = this.dataProviderService
.getDataProvider(assetProfile.dataSource)
.getDataProviderInfo();
}
return {
marketData,
assetProfile: assetProfile ?? {
activitiesCount,
currency,
dataSource,
dateOfFirstActivity,
symbol,
currency: '-'
isActive: true
}
};
}
public async patchAssetProfileData({
assetClass,
assetSubClass,
comment,
countries,
currency,
dataSource,
name,
scraperConfiguration,
sectors,
symbol,
symbolMapping
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
await this.symbolProfileService.updateSymbolProfile({
public async getUsers({
skip,
take = Number.MAX_SAFE_INTEGER
}: {
skip?: number;
take?: number;
}): Promise<AdminUsers> {
const [count, users] = await Promise.all([
this.countUsersWithAnalytics(),
this.getUsersWithAnalytics({ skip, take })
]);
return { count, users };
}
public async patchAssetProfileData(
{ dataSource, symbol }: AssetProfileIdentifier,
{
assetClass,
assetSubClass,
comment,
countries,
currency,
dataSource,
dataSource: newDataSource,
holdings,
isActive,
name,
scraperConfiguration,
sectors,
symbol,
symbolMapping
});
symbol: newSymbol,
symbolMapping,
url
}: Prisma.SymbolProfileUpdateInput
) {
if (
newSymbol &&
newDataSource &&
(newSymbol !== symbol || newDataSource !== dataSource)
) {
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([
{
dataSource: DataSource[newDataSource.toString()],
symbol: newSymbol as string
}
]);
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([
{
dataSource,
symbol
if (assetProfile) {
throw new HttpException(
getReasonPhrase(StatusCodes.CONFLICT),
StatusCodes.CONFLICT
);
}
]);
return symbolProfile;
try {
Promise.all([
await this.symbolProfileService.updateAssetProfileIdentifier(
{
dataSource,
symbol
},
{
dataSource: DataSource[newDataSource.toString()],
symbol: newSymbol as string
}
),
await this.marketDataService.updateAssetProfileIdentifier(
{
dataSource,
symbol
},
{
dataSource: DataSource[newDataSource.toString()],
symbol: newSymbol as string
}
)
]);
return this.symbolProfileService.getSymbolProfiles([
{
dataSource: DataSource[newDataSource.toString()],
symbol: newSymbol as string
}
])?.[0];
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.BAD_REQUEST),
StatusCodes.BAD_REQUEST
);
}
} else {
const symbolProfileOverrides = {
assetClass: assetClass as AssetClass,
assetSubClass: assetSubClass as AssetSubClass,
name: name as string,
url: url as string
};
const updatedSymbolProfile: Prisma.SymbolProfileUpdateInput = {
comment,
countries,
currency,
dataSource,
holdings,
isActive,
scraperConfiguration,
sectors,
symbol,
symbolMapping,
...(dataSource === 'MANUAL'
? { assetClass, assetSubClass, name, url }
: {
SymbolProfileOverrides: {
upsert: {
create: symbolProfileOverrides,
update: symbolProfileOverrides
}
}
})
};
await this.symbolProfileService.updateSymbolProfile(
{
dataSource,
symbol
},
updatedSymbolProfile
);
return this.symbolProfileService.getSymbolProfiles([
{
dataSource: dataSource as DataSource,
symbol: symbol as string
}
])?.[0];
}
}
public async putSetting(key: string, value: string) {
@ -373,15 +641,124 @@ export class AdminService {
return response;
}
private async getMarketDataForCurrencies(): Promise<AdminMarketData> {
const marketDataItems = await this.prismaService.marketData.groupBy({
_count: true,
by: ['dataSource', 'symbol']
private async countUsersWithAnalytics() {
let where: Prisma.UserWhereInput;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
where = {
NOT: {
analytics: null
}
};
}
return this.prismaService.user.count({
where
});
}
private getExtendedPrismaClient() {
Logger.debug('Connect extended prisma client', 'AdminService');
const symbolProfileExtension = Prisma.defineExtension((client) => {
return client.$extends({
result: {
symbolProfile: {
isUsedByUsersWithSubscription: {
compute: async ({ id }) => {
const { _count } =
await this.prismaService.symbolProfile.findUnique({
select: {
_count: {
select: {
activities: {
where: {
user: {
subscriptions: {
some: {
expiresAt: {
gt: new Date()
}
}
}
}
}
}
}
}
},
where: {
id
}
});
return _count.activities > 0;
}
}
}
}
});
});
const marketData: AdminMarketDataItem[] = this.exchangeRateDataService
.getCurrencyPairs()
.map(({ dataSource, symbol }) => {
return new PrismaClient().$extends(symbolProfileExtension);
}
private async getMarketDataForCurrencies(): Promise<AdminMarketData> {
const currencyPairs = this.exchangeRateDataService.getCurrencyPairs();
const [lastMarketPrices, marketDataItems] = await Promise.all([
this.prismaService.marketData.findMany({
distinct: ['dataSource', 'symbol'],
orderBy: { date: 'desc' },
select: {
dataSource: true,
marketPrice: true,
symbol: true
},
where: {
dataSource: {
in: currencyPairs.map(({ dataSource }) => {
return dataSource;
})
},
symbol: {
in: currencyPairs.map(({ symbol }) => {
return symbol;
})
}
}
}),
this.prismaService.marketData.groupBy({
_count: true,
by: ['dataSource', 'symbol']
})
]);
const lastMarketPriceMap = new Map<string, number>();
for (const { dataSource, marketPrice, symbol } of lastMarketPrices) {
lastMarketPriceMap.set(
getAssetProfileIdentifier({ dataSource, symbol }),
marketPrice
);
}
const marketDataPromise: Promise<AdminMarketDataItem>[] = currencyPairs.map(
async ({ dataSource, symbol }) => {
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
if (isCurrency(getCurrencyFromSymbol(symbol))) {
currency = getCurrencyFromSymbol(symbol);
({ activitiesCount, dateOfFirstActivity } =
await this.orderService.getStatisticsByCurrency(currency));
}
const lastMarketPrice = lastMarketPriceMap.get(
getAssetProfileIdentifier({ dataSource, symbol })
);
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
@ -391,86 +768,113 @@ export class AdminService {
})?._count ?? 0;
return {
activitiesCount,
currency,
dataSource,
lastMarketPrice,
marketDataItemCount,
symbol,
assetClass: 'CASH',
assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH,
countriesCount: 0,
currency: symbol.replace(DEFAULT_CURRENCY, ''),
date: dateOfFirstActivity,
id: undefined,
isActive: true,
name: symbol,
sectorsCount: 0
sectorsCount: 0,
watchedByCount: 0
};
});
}
);
const marketData = await Promise.all(marketDataPromise);
return { marketData, count: marketData.length };
}
private async getUsersWithAnalytics(): Promise<AdminData['users']> {
let orderBy: any = {
private async getUsersWithAnalytics({
skip,
take
}: {
skip?: number;
take?: number;
}): Promise<AdminUsers['users']> {
let orderBy: Prisma.UserOrderByWithRelationInput = {
createdAt: 'desc'
};
let where;
let where: Prisma.UserWhereInput;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
orderBy = {
Analytics: {
updatedAt: 'desc'
analytics: {
lastRequestAt: 'desc'
}
};
where = {
NOT: {
Analytics: null
analytics: null
}
};
}
const usersWithAnalytics = await this.prismaService.user.findMany({
orderBy,
skip,
take,
where,
select: {
_count: {
select: { Account: true, Order: true }
select: { accounts: true, activities: true }
},
Analytics: {
analytics: {
select: {
activityCount: true,
country: true,
dataProviderGhostfolioDailyRequests: true,
updatedAt: true
}
},
createdAt: true,
id: true,
Subscription: true
},
take: 30
role: true,
subscriptions: {
orderBy: {
expiresAt: 'desc'
},
take: 1,
where: {
expiresAt: {
gt: new Date()
}
}
}
}
});
return usersWithAnalytics.map(
({ _count, Analytics, createdAt, id, Subscription }) => {
({ _count, analytics, createdAt, id, role, subscriptions }) => {
const daysSinceRegistration =
differenceInDays(new Date(), createdAt) + 1;
const engagement = Analytics
? Analytics.activityCount / daysSinceRegistration
const engagement = analytics
? analytics.activityCount / daysSinceRegistration
: undefined;
const subscription = this.configurationService.get(
'ENABLE_FEATURE_SUBSCRIPTION'
)
? this.subscriptionService.getSubscription({
createdAt,
subscriptions: Subscription
})
: undefined;
const subscription =
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
subscriptions?.length > 0
? subscriptions[0]
: undefined;
return {
createdAt,
engagement,
id,
role,
subscription,
accountCount: _count.Account || 0,
country: Analytics?.country,
lastActivity: Analytics?.updatedAt,
transactionCount: _count.Order || 0
accountCount: _count.accounts || 0,
activityCount: _count.activities || 0,
country: analytics?.country,
dailyApiRequests: analytics?.dataProviderGhostfolioDailyRequests || 0,
lastActivity: analytics?.updatedAt
};
}
);

View File

@ -26,7 +26,7 @@ export class QueueController {
public async deleteJobs(
@Query('status') filterByStatus?: string
): Promise<void> {
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined;
const status = (filterByStatus?.split(',') as JobStatus[]) ?? undefined;
return this.queueService.deleteJobs({ status });
}
@ -36,7 +36,7 @@ export class QueueController {
public async getJobs(
@Query('status') filterByStatus?: string
): Promise<AdminJobs> {
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined;
const status = (filterByStatus?.split(',') as JobStatus[]) ?? undefined;
return this.queueService.getJobs({ status });
}
@ -46,4 +46,11 @@ export class QueueController {
public async deleteJob(@Param('id') id: string): Promise<void> {
return this.queueService.deleteJob(id);
}
@Get('job/:id/execute')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async executeJob(@Param('id') id: string): Promise<void> {
return this.queueService.executeJob(id);
}
}

View File

@ -1,4 +1,5 @@
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { Module } from '@nestjs/common';
@ -7,7 +8,7 @@ import { QueueService } from './queue.service';
@Module({
controllers: [QueueController],
imports: [DataGatheringModule],
imports: [DataGatheringModule, PortfolioSnapshotQueueModule],
providers: [QueueService]
})
export class QueueModule {}

View File

@ -1,5 +1,6 @@
import {
DATA_GATHERING_QUEUE,
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE,
QUEUE_JOB_STATUS_LIST
} from '@ghostfolio/common/config';
import { AdminJobs } from '@ghostfolio/common/interfaces';
@ -12,11 +13,19 @@ import { JobStatus, Queue } from 'bull';
export class QueueService {
public constructor(
@InjectQueue(DATA_GATHERING_QUEUE)
private readonly dataGatheringQueue: Queue
private readonly dataGatheringQueue: Queue,
@InjectQueue(PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE)
private readonly portfolioSnapshotQueue: Queue
) {}
public async deleteJob(aId: string) {
return (await this.dataGatheringQueue.getJob(aId))?.remove();
let job = await this.dataGatheringQueue.getJob(aId);
if (!job) {
job = await this.portfolioSnapshotQueue.getJob(aId);
}
return job?.remove();
}
public async deleteJobs({
@ -25,13 +34,23 @@ export class QueueService {
status?: JobStatus[];
}) {
for (const statusItem of status) {
await this.dataGatheringQueue.clean(
300,
statusItem === 'waiting' ? 'wait' : statusItem
);
const queueStatus = statusItem === 'waiting' ? 'wait' : statusItem;
await this.dataGatheringQueue.clean(300, queueStatus);
await this.portfolioSnapshotQueue.clean(300, queueStatus);
}
}
public async executeJob(aId: string) {
let job = await this.dataGatheringQueue.getJob(aId);
if (!job) {
job = await this.portfolioSnapshotQueue.getJob(aId);
}
return job?.promote();
}
public async getJobs({
limit = 1000,
status = QUEUE_JOB_STATUS_LIST
@ -39,10 +58,13 @@ export class QueueService {
limit?: number;
status?: JobStatus[];
}): Promise<AdminJobs> {
const jobs = await this.dataGatheringQueue.getJobs(status);
const [dataGatheringJobs, portfolioSnapshotJobs] = await Promise.all([
this.dataGatheringQueue.getJobs(status),
this.portfolioSnapshotQueue.getJobs(status)
]);
const jobsWithState = await Promise.all(
jobs
[...dataGatheringJobs, ...portfolioSnapshotJobs]
.filter((job) => {
return job;
})
@ -54,6 +76,7 @@ export class QueueService {
finishedOn: job.finishedOn,
id: job.id,
name: job.name,
opts: job.opts,
stacktrace: job.stacktrace,
state: await job.getState(),
timestamp: job.timestamp

View File

@ -1,11 +1,14 @@
import { AssetClass, AssetSubClass, Prisma } from '@prisma/client';
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { AssetClass, AssetSubClass, DataSource, Prisma } from '@prisma/client';
import {
IsArray,
IsBoolean,
IsEnum,
IsISO4217CurrencyCode,
IsObject,
IsOptional,
IsString
IsString,
IsUrl
} from 'class-validator';
export class UpdateAssetProfileDto {
@ -17,20 +20,28 @@ export class UpdateAssetProfileDto {
@IsOptional()
assetSubClass?: AssetSubClass;
@IsString()
@IsOptional()
@IsString()
comment?: string;
@IsArray()
@IsOptional()
countries?: Prisma.InputJsonArray;
@IsISO4217CurrencyCode()
@IsCurrencyCode()
@IsOptional()
currency?: string;
@IsString()
@IsEnum(DataSource)
@IsOptional()
dataSource?: DataSource;
@IsBoolean()
@IsOptional()
isActive?: boolean;
@IsOptional()
@IsString()
name?: string;
@IsObject()
@ -41,9 +52,20 @@ export class UpdateAssetProfileDto {
@IsOptional()
sectors?: Prisma.InputJsonArray;
@IsOptional()
@IsString()
symbol?: string;
@IsObject()
@IsOptional()
symbolMapping?: {
[dataProvider: string]: string;
};
@IsOptional()
@IsUrl({
protocols: ['https'],
require_protocol: true
})
url?: string;
}

View File

@ -1,5 +1,5 @@
import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, isNotEmptyObject } from 'class-validator';
import { ArrayNotEmpty, IsArray } from 'class-validator';
import { UpdateMarketDataDto } from './update-market-data.dto';

View File

@ -1,19 +1,23 @@
import { EventsModule } from '@ghostfolio/api/events/events.module';
import { HtmlTemplateMiddleware } from '@ghostfolio/api/middlewares/html-template.middleware';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { CronService } from '@ghostfolio/api/services/cron.service';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { CronModule } from '@ghostfolio/api/services/cron/cron.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 { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import {
DEFAULT_LANGUAGE_CODE,
SUPPORTED_LANGUAGE_CODES
} from '@ghostfolio/common/config';
import { BullModule } from '@nestjs/bull';
import { Module } from '@nestjs/common';
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
import { ServeStaticModule } from '@nestjs/serve-static';
import { StatusCodes } from 'http-status-codes';
@ -23,10 +27,20 @@ import { AccessModule } from './access/access.module';
import { AccountModule } from './account/account.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 { BenchmarkModule } from './benchmark/benchmark.module';
import { CacheModule } from './cache/cache.module';
import { AiModule } from './endpoints/ai/ai.module';
import { ApiKeysModule } from './endpoints/api-keys/api-keys.module';
import { AssetsModule } from './endpoints/assets/assets.module';
import { BenchmarksModule } from './endpoints/benchmarks/benchmarks.module';
import { GhostfolioModule } from './endpoints/data-providers/ghostfolio/ghostfolio.module';
import { MarketDataModule } from './endpoints/market-data/market-data.module';
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';
@ -37,20 +51,23 @@ import { OrderModule } from './order/order.module';
import { PlatformModule } from './platform/platform.module';
import { PortfolioModule } from './portfolio/portfolio.module';
import { RedisCacheModule } from './redis-cache/redis-cache.module';
import { SitemapModule } from './sitemap/sitemap.module';
import { SubscriptionModule } from './subscription/subscription.module';
import { SymbolModule } from './symbol/symbol.module';
import { TagModule } from './tag/tag.module';
import { UserModule } from './user/user.module';
@Module({
controllers: [AppController],
imports: [
AdminModule,
AccessModule,
AccountModule,
AiModule,
ApiKeysModule,
AssetModule,
AssetsModule,
AuthDeviceModule,
AuthModule,
BenchmarkModule,
BenchmarksModule,
BullModule.forRoot({
redis: {
db: parseInt(process.env.REDIS_DB ?? '0', 10),
@ -62,24 +79,31 @@ import { UserModule } from './user/user.module';
CacheModule,
ConfigModule.forRoot(),
ConfigurationModule,
CronModule,
DataGatheringModule,
DataProviderModule,
EventEmitterModule.forRoot(),
EventsModule,
ExchangeRateModule,
ExchangeRateDataModule,
ExportModule,
GhostfolioModule,
HealthModule,
ImportModule,
InfoModule,
LogoModule,
MarketDataModule,
OrderModule,
PlatformModule,
PortfolioModule,
PortfolioSnapshotQueueModule,
PrismaModule,
PropertyModule,
PublicModule,
RedisCacheModule,
ScheduleModule.forRoot(),
ServeStaticModule.forRoot({
exclude: ['/api*', '/sitemap.xml'],
exclude: ['/.well-known/*wildcard', '/api/*wildcard', '/sitemap.xml'],
rootPath: join(__dirname, '..', 'client'),
serveStaticOptions: {
setHeaders: (res) => {
@ -102,14 +126,21 @@ import { UserModule } from './user/user.module';
}
}
}),
ServeStaticModule.forRoot({
rootPath: join(__dirname, '..', 'client', '.well-known'),
serveRoot: '/.well-known'
}),
SitemapModule,
SubscriptionModule,
SymbolModule,
TagModule,
TwitterBotModule,
UserModule
TagsModule,
UserModule,
WatchlistModule
],
controllers: [AppController],
providers: [CronService]
providers: [I18nService]
})
export class AppModule {}
export class AppModule implements NestModule {
public configure(consumer: MiddlewareConsumer) {
consumer.apply(HtmlTemplateMiddleware).forRoutes('*wildcard');
}
}

View File

@ -0,0 +1,29 @@
import { AdminService } from '@ghostfolio/api/app/admin/admin.service';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import type { AdminMarketDataDetails } from '@ghostfolio/common/interfaces';
import { Controller, Get, Param, UseInterceptors } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import { pick } from 'lodash';
@Controller('asset')
export class AssetController {
public constructor(private readonly adminService: AdminService) {}
@Get(':dataSource/:symbol')
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getAsset(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<AdminMarketDataDetails> {
const { assetProfile, marketData } =
await this.adminService.getMarketDataBySymbol({ dataSource, symbol });
return {
marketData,
assetProfile: pick(assetProfile, ['dataSource', 'name', 'symbol'])
};
}
}

View File

@ -0,0 +1,17 @@
import { AdminModule } from '@ghostfolio/api/app/admin/admin.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 { Module } from '@nestjs/common';
import { AssetController } from './asset.controller';
@Module({
controllers: [AssetController],
imports: [
AdminModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
]
})
export class AssetModule {}

View File

@ -1,6 +1,5 @@
import { AuthDeviceController } from '@ghostfolio/api/app/auth-device/auth-device.controller';
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { Module } from '@nestjs/common';
@ -9,7 +8,6 @@ import { JwtModule } from '@nestjs/jwt';
@Module({
controllers: [AuthDeviceController],
imports: [
ConfigurationModule,
JwtModule.register({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '180 days' }

View File

@ -1,4 +1,3 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { Injectable } from '@nestjs/common';
@ -6,10 +5,7 @@ import { AuthDevice, Prisma } from '@prisma/client';
@Injectable()
export class AuthDeviceService {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly prismaService: PrismaService
) {}
public constructor(private readonly prismaService: PrismaService) {}
public async authDevice(
where: Prisma.AuthDeviceWhereUniqueInput

View File

@ -0,0 +1,70 @@
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { HEADER_KEY_TOKEN } from '@ghostfolio/common/config';
import { hasRole } from '@ghostfolio/common/permissions';
import { HttpException, Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { HeaderAPIKeyStrategy } from 'passport-headerapikey';
@Injectable()
export class ApiKeyStrategy extends PassportStrategy(
HeaderAPIKeyStrategy,
'api-key'
) {
public constructor(
private readonly apiKeyService: ApiKeyService,
private readonly configurationService: ConfigurationService,
private readonly prismaService: PrismaService,
private readonly userService: UserService
) {
super({ header: HEADER_KEY_TOKEN, prefix: 'Api-Key ' }, false);
}
public async validate(apiKey: string) {
const user = await this.validateApiKey(apiKey);
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
if (hasRole(user, 'INACTIVE')) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
await this.prismaService.analytics.upsert({
create: { user: { connect: { id: user.id } } },
update: {
activityCount: { increment: 1 },
lastRequestAt: new Date()
},
where: { userId: user.id }
});
}
return user;
}
private async validateApiKey(apiKey: string) {
if (!apiKey) {
throw new HttpException(
getReasonPhrase(StatusCodes.UNAUTHORIZED),
StatusCodes.UNAUTHORIZED
);
}
try {
const { id } = await this.apiKeyService.getUserByApiKey(apiKey);
return this.userService.user({ id });
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.UNAUTHORIZED),
StatusCodes.UNAUTHORIZED
);
}
}
}

View File

@ -14,12 +14,12 @@ import {
Req,
Res,
UseGuards,
VERSION_NEUTRAL,
Version
Version,
VERSION_NEUTRAL
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Request, Response } from 'express';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import { AuthService } from './auth.service';
import {
@ -85,7 +85,7 @@ export class AuthController {
@Res() response: Response
) {
// Handles the Google OAuth2 callback
const jwt: string = (<any>request.user).jwt;
const jwt: string = (request.user as any).jwt;
if (jwt) {
response.redirect(
@ -130,10 +130,7 @@ export class AuthController {
public async verifyAttestation(
@Body() body: { deviceName: string; credential: AttestationCredentialJSON }
) {
return this.webAuthService.verifyAttestation(
body.deviceName,
body.credential
);
return this.webAuthService.verifyAttestation(body.credential);
}
@Post('webauthn/generate-assertion-options')

View File

@ -2,6 +2,7 @@ import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.s
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
@ -9,6 +10,7 @@ import { PropertyModule } from '@ghostfolio/api/services/property/property.modul
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { ApiKeyStrategy } from './api-key.strategy';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { GoogleStrategy } from './google.strategy';
@ -28,6 +30,8 @@ import { JwtStrategy } from './jwt.strategy';
UserModule
],
providers: [
ApiKeyService,
ApiKeyStrategy,
AuthDeviceService,
AuthService,
GoogleStrategy,

View File

@ -20,10 +20,10 @@ export class AuthService {
public async validateAnonymousLogin(accessToken: string): Promise<string> {
return new Promise(async (resolve, reject) => {
try {
const hashedAccessToken = this.userService.createAccessToken(
accessToken,
this.configurationService.get('ACCESS_TOKEN_SALT')
);
const hashedAccessToken = this.userService.createAccessToken({
password: accessToken,
salt: this.configurationService.get('ACCESS_TOKEN_SALT')
});
const [user] = await this.userService.users({
where: { accessToken: hashedAccessToken }

View File

@ -3,7 +3,7 @@ import { ConfigurationService } from '@ghostfolio/api/services/configuration/con
import { Injectable, Logger } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Provider } from '@prisma/client';
import { Strategy } from 'passport-google-oauth20';
import { Profile, Strategy } from 'passport-google-oauth20';
import { AuthService } from './auth.service';
@ -11,7 +11,7 @@ import { AuthService } from './auth.service';
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
public constructor(
private readonly authService: AuthService,
readonly configurationService: ConfigurationService
configurationService: ConfigurationService
) {
super({
callbackURL: `${configurationService.get(
@ -20,28 +20,24 @@ export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
clientID: configurationService.get('GOOGLE_CLIENT_ID'),
clientSecret: configurationService.get('GOOGLE_SECRET'),
passReqToCallback: true,
scope: ['email', 'profile']
scope: ['profile']
});
}
public async validate(
request: any,
token: string,
refreshToken: string,
profile,
done: Function,
done2: Function
_request: any,
_token: string,
_refreshToken: string,
profile: Profile,
done: Function
) {
try {
const jwt: string = await this.authService.validateOAuthLogin({
const jwt = await this.authService.validateOAuthLogin({
provider: Provider.GOOGLE,
thirdPartyId: profile.id
});
const user = {
jwt
};
done(null, user);
done(null, { jwt });
} catch (error) {
Logger.error(error, 'GoogleStrategy');
done(error, false);

View File

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

View File

@ -1,11 +1,17 @@
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { HEADER_KEY_TIMEZONE } from '@ghostfolio/common/config';
import {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE,
HEADER_KEY_TIMEZONE
} from '@ghostfolio/common/config';
import { hasRole } from '@ghostfolio/common/permissions';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { HttpException, Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import * as countriesAndTimezones from 'countries-and-timezones';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { ExtractJwt, Strategy } from 'passport-jwt';
@Injectable()
@ -29,26 +35,51 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
if (user) {
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
if (hasRole(user, 'INACTIVE')) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
const country =
countriesAndTimezones.getCountryForTimezone(timezone)?.id;
await this.prismaService.analytics.upsert({
create: { country, User: { connect: { id: user.id } } },
create: { country, user: { connect: { id: user.id } } },
update: {
country,
activityCount: { increment: 1 },
updatedAt: new Date()
lastRequestAt: new Date()
},
where: { userId: user.id }
});
}
if (!user.Settings.settings.baseCurrency) {
user.Settings.settings.baseCurrency = DEFAULT_CURRENCY;
}
if (!user.Settings.settings.language) {
user.Settings.settings.language = DEFAULT_LANGUAGE_CODE;
}
return user;
} else {
throw '';
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
} catch (error) {
if (error?.getStatus?.() === StatusCodes.TOO_MANY_REQUESTS) {
throw error;
} else {
throw new HttpException(
getReasonPhrase(StatusCodes.UNAUTHORIZED),
StatusCodes.UNAUTHORIZED
);
}
} catch (err) {
throw new UnauthorizedException('unauthorized', err.message);
}
}
}

View File

@ -13,17 +13,18 @@ import {
import { REQUEST } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';
import {
generateAuthenticationOptions,
GenerateAuthenticationOptionsOpts,
generateRegistrationOptions,
GenerateRegistrationOptionsOpts,
VerifiedAuthenticationResponse,
VerifiedRegistrationResponse,
VerifyAuthenticationResponseOpts,
VerifyRegistrationResponseOpts,
generateAuthenticationOptions,
generateRegistrationOptions,
verifyAuthenticationResponse,
verifyRegistrationResponse
VerifyAuthenticationResponseOpts,
verifyRegistrationResponse,
VerifyRegistrationResponseOpts
} from '@simplewebauthn/server';
import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers';
import {
AssertionCredentialJSON,
@ -41,7 +42,7 @@ export class WebAuthService {
) {}
get rpID() {
return this.configurationService.get('WEB_AUTH_RP_ID');
return new URL(this.configurationService.get('ROOT_URL')).hostname;
}
get expectedOrigin() {
@ -54,10 +55,9 @@ export class WebAuthService {
const opts: GenerateRegistrationOptionsOpts = {
rpName: 'Ghostfolio',
rpID: this.rpID,
userID: user.id,
userID: isoUint8Array.fromUTF8String(user.id),
userName: '',
timeout: 60000,
attestationType: 'indirect',
authenticatorSelection: {
authenticatorAttachment: 'platform',
requireResidentKey: false,
@ -80,7 +80,6 @@ export class WebAuthService {
}
public async verifyAttestation(
deviceName: string,
credential: AttestationCredentialJSON
): Promise<AuthDeviceDto> {
const user = this.request.user;
@ -112,11 +111,17 @@ export class WebAuthService {
where: { userId: user.id }
});
if (registrationInfo && verified) {
const { counter, credentialID, credentialPublicKey } = registrationInfo;
const {
credential: {
counter,
id: credentialId,
publicKey: credentialPublicKey
}
} = registrationInfo;
let existingDevice = devices.find(
(device) => device.credentialId === credentialID
);
let existingDevice = devices.find((device) => {
return isoBase64URL.fromBuffer(device.credentialId) === credentialId;
});
if (!existingDevice) {
/**
@ -124,9 +129,9 @@ export class WebAuthService {
*/
existingDevice = await this.deviceService.createAuthDevice({
counter,
credentialId: Buffer.from(credentialID),
credentialId: Buffer.from(credentialId),
credentialPublicKey: Buffer.from(credentialPublicKey),
User: { connect: { id: user.id } }
user: { connect: { id: user.id } }
});
}
@ -149,9 +154,8 @@ export class WebAuthService {
const opts: GenerateAuthenticationOptionsOpts = {
allowCredentials: [
{
id: device.credentialId,
transports: ['internal'],
type: 'public-key'
id: isoBase64URL.fromBuffer(device.credentialId),
transports: ['internal']
}
],
rpID: this.rpID,
@ -188,10 +192,10 @@ export class WebAuthService {
let verification: VerifiedAuthenticationResponse;
try {
const opts: VerifyAuthenticationResponseOpts = {
authenticator: {
credentialID: device.credentialId,
credentialPublicKey: device.credentialPublicKey,
counter: device.counter
credential: {
counter: device.counter,
id: isoBase64URL.fromBuffer(device.credentialId),
publicKey: device.credentialPublicKey
},
expectedChallenge: `${user.authChallenge}`,
expectedOrigin: this.expectedOrigin,

View File

@ -14,6 +14,6 @@ export class CacheController {
@Post('flush')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async flushCache(): Promise<void> {
return this.redisCacheService.reset();
await this.redisCacheService.reset();
}
}

View File

@ -1,10 +1,4 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.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 { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
@ -12,14 +6,6 @@ import { CacheController } from './cache.controller';
@Module({
controllers: [CacheController],
imports: [
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
ExchangeRateDataModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule
]
imports: [RedisCacheModule]
})
export class CacheModule {}

View File

@ -0,0 +1,59 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { AiPromptResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { AiPromptMode, RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Get,
Inject,
Param,
Query,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { AiService } from './ai.service';
@Controller('ai')
export class AiController {
public constructor(
private readonly aiService: AiService,
private readonly apiService: ApiService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get('prompt/:mode')
@HasPermission(permissions.readAiPrompt)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getPrompt(
@Param('mode') mode: AiPromptMode,
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string,
@Query('tags') filterByTags?: string
): Promise<AiPromptResponse> {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByDataSource,
filterBySymbol,
filterByTags
});
const prompt = await this.aiService.getPrompt({
filters,
mode,
impersonationId: undefined,
languageCode: this.request.user.Settings.settings.language,
userCurrency: this.request.user.Settings.settings.baseCurrency,
userId: this.request.user.id
});
return { prompt };
}
}

View File

@ -0,0 +1,59 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
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 { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.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 { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.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 { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { AiController } from './ai.controller';
import { AiService } from './ai.service';
@Module({
controllers: [AiController],
imports: [
ApiModule,
BenchmarkModule,
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
I18nModule,
ImpersonationModule,
MarketDataModule,
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
PropertyModule,
RedisCacheModule,
SymbolProfileModule,
UserModule
],
providers: [
AccountBalanceService,
AccountService,
AiService,
CurrentRateService,
MarketDataService,
PortfolioCalculatorFactory,
PortfolioService,
RulesService
]
})
export class AiModule {}

View File

@ -0,0 +1,100 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import {
PROPERTY_API_KEY_OPENROUTER,
PROPERTY_OPENROUTER_MODEL
} from '@ghostfolio/common/config';
import { Filter } from '@ghostfolio/common/interfaces';
import type { AiPromptMode } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
import { generateText } from 'ai';
@Injectable()
export class AiService {
public constructor(
private readonly portfolioService: PortfolioService,
private readonly propertyService: PropertyService
) {}
public async generateText({ prompt }: { prompt: string }) {
const openRouterApiKey = await this.propertyService.getByKey<string>(
PROPERTY_API_KEY_OPENROUTER
);
const openRouterModel = await this.propertyService.getByKey<string>(
PROPERTY_OPENROUTER_MODEL
);
const openRouterService = createOpenRouter({
apiKey: openRouterApiKey
});
return generateText({
prompt,
model: openRouterService.chat(openRouterModel)
});
}
public async getPrompt({
filters,
impersonationId,
languageCode,
mode,
userCurrency,
userId
}: {
filters?: Filter[];
impersonationId: string;
languageCode: string;
mode: AiPromptMode;
userCurrency: string;
userId: string;
}) {
const { holdings } = await this.portfolioService.getDetails({
filters,
impersonationId,
userId
});
const holdingsTable = [
'| Name | Symbol | Currency | Asset Class | Asset Sub Class | Allocation in Percentage |',
'| --- | --- | --- | --- | --- | --- |',
...Object.values(holdings)
.sort((a, b) => {
return b.allocationInPercentage - a.allocationInPercentage;
})
.map(
({
allocationInPercentage,
assetClass,
assetSubClass,
currency,
name,
symbol
}) => {
return `| ${name} | ${symbol} | ${currency} | ${assetClass} | ${assetSubClass} | ${(allocationInPercentage * 100).toFixed(3)}% |`;
}
)
];
if (mode === 'portfolio') {
return holdingsTable.join('\n');
}
return [
`You are a neutral financial assistant. Please analyze the following investment portfolio (base currency being ${userCurrency}) in simple words.`,
...holdingsTable,
'Structure your answer with these sections:',
'Overview: Briefly summarize the portfolios composition and allocation rationale.',
'Risk Assessment: Identify potential risks, including market volatility, concentration, and sectoral imbalances.',
'Advantages: Highlight strengths, focusing on growth potential, diversification, or other benefits.',
'Disadvantages: Point out weaknesses, such as overexposure or lack of defensive assets.',
'Target Group: Discuss who this portfolio might suit (e.g., risk tolerance, investment goals, life stages, and experience levels).',
'Optimization Ideas: Offer ideas to complement the portfolio, ensuring they are constructive and neutral in tone.',
'Conclusion: Provide a concise summary highlighting key insights.',
`Provide your answer in the following language: ${languageCode}.`
].join('\n');
}
}

View File

@ -0,0 +1,25 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ApiKeyService } from '@ghostfolio/api/services/api-key/api-key.service';
import { ApiKeyResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Inject, Post, UseGuards } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
@Controller('api-keys')
export class ApiKeysController {
public constructor(
private readonly apiKeyService: ApiKeyService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@HasPermission(permissions.createApiKey)
@Post()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createApiKey(): Promise<ApiKeyResponse> {
return this.apiKeyService.create({ userId: this.request.user.id });
}
}

View File

@ -0,0 +1,11 @@
import { ApiKeyModule } from '@ghostfolio/api/services/api-key/api-key.module';
import { Module } from '@nestjs/common';
import { ApiKeysController } from './api-keys.controller';
@Module({
controllers: [ApiKeysController],
imports: [ApiKeyModule]
})
export class ApiKeysModule {}

View File

@ -0,0 +1,46 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { interpolate } from '@ghostfolio/common/helper';
import {
Controller,
Get,
Param,
Res,
Version,
VERSION_NEUTRAL
} from '@nestjs/common';
import { Response } from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
@Controller('assets')
export class AssetsController {
private webManifest = '';
public constructor(
public readonly configurationService: ConfigurationService
) {
try {
this.webManifest = readFileSync(
join(__dirname, 'assets', 'site.webmanifest'),
'utf8'
);
} catch {}
}
@Get('/:languageCode/site.webmanifest')
@Version(VERSION_NEUTRAL)
public getWebManifest(
@Param('languageCode') languageCode: string,
@Res() response: Response
): void {
const rootUrl = this.configurationService.get('ROOT_URL');
const webManifest = interpolate(this.webManifest, {
languageCode,
rootUrl
});
response.setHeader('Content-Type', 'application/json');
response.send(webManifest);
}
}

View File

@ -0,0 +1,11 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { Module } from '@nestjs/common';
import { AssetsController } from './assets.controller';
@Module({
controllers: [AssetsController],
providers: [ConfigurationService]
})
export class AssetsModule {}

View File

@ -1,24 +1,30 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import type {
AssetProfileIdentifier,
BenchmarkMarketDataDetails,
BenchmarkResponse,
UniqueAsset
BenchmarkResponse
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
Delete,
Get,
Headers,
HttpException,
Inject,
Param,
Post,
Query,
UseGuards,
UseInterceptors
} from '@nestjs/common';
@ -27,19 +33,23 @@ import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { BenchmarkService } from './benchmark.service';
import { BenchmarksService } from './benchmarks.service';
@Controller('benchmark')
export class BenchmarkController {
@Controller('benchmarks')
export class BenchmarksController {
public constructor(
private readonly apiService: ApiService,
private readonly benchmarkService: BenchmarkService,
private readonly benchmarksService: BenchmarksService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@HasPermission(permissions.accessAdminControl)
@Post()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) {
public async addBenchmark(
@Body() { dataSource, symbol }: AssetProfileIdentifier
) {
try {
const benchmark = await this.benchmarkService.addBenchmark({
dataSource,
@ -103,19 +113,44 @@ export class BenchmarkController {
@Get(':dataSource/:symbol/:startDateString')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getBenchmarkMarketDataBySymbol(
public async getBenchmarkMarketDataForUser(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Param('dataSource') dataSource: DataSource,
@Param('startDateString') startDateString: string,
@Param('symbol') symbol: string
@Param('symbol') symbol: string,
@Query('range') dateRange: DateRange = 'max',
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string,
@Query('tags') filterByTags?: string,
@Query('withExcludedAccounts') withExcludedAccountsParam = 'false'
): Promise<BenchmarkMarketDataDetails> {
const startDate = new Date(startDateString);
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const { endDate, startDate } = getIntervalFromDateRange(
dateRange,
new Date(startDateString)
);
return this.benchmarkService.getMarketDataBySymbol({
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByDataSource,
filterBySymbol,
filterByTags
});
const withExcludedAccounts = withExcludedAccountsParam === 'true';
return this.benchmarksService.getMarketDataForUser({
dataSource,
dateRange,
endDate,
filters,
impersonationId,
startDate,
symbol,
userCurrency
withExcludedAccounts,
user: this.request.user
});
}
}

View File

@ -0,0 +1,65 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
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';
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';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.service';
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 { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.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 { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { BenchmarksController } from './benchmarks.controller';
import { BenchmarksService } from './benchmarks.service';
@Module({
controllers: [BenchmarksController],
imports: [
ApiModule,
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
I18nModule,
ImpersonationModule,
MarketDataModule,
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
PropertyModule,
RedisCacheModule,
SymbolModule,
SymbolProfileModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule,
UserModule
],
providers: [
AccountBalanceService,
AccountService,
BenchmarkService,
BenchmarksService,
CurrentRateService,
MarketDataService,
PortfolioCalculatorFactory,
PortfolioService,
RulesService
]
})
export class BenchmarksModule {}

View File

@ -0,0 +1,163 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.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 { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
import {
AssetProfileIdentifier,
BenchmarkMarketDataDetails,
Filter
} from '@ghostfolio/common/interfaces';
import { DateRange, UserWithSettings } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { format, isSameDay } from 'date-fns';
import { isNumber } from 'lodash';
@Injectable()
export class BenchmarksService {
public constructor(
private readonly benchmarkService: BenchmarkService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly marketDataService: MarketDataService,
private readonly portfolioService: PortfolioService,
private readonly symbolService: SymbolService
) {}
public async getMarketDataForUser({
dataSource,
dateRange,
endDate = new Date(),
filters,
impersonationId,
startDate,
symbol,
user,
withExcludedAccounts
}: {
dateRange: DateRange;
endDate?: Date;
filters?: Filter[];
impersonationId: string;
startDate: Date;
user: UserWithSettings;
withExcludedAccounts?: boolean;
} & AssetProfileIdentifier): Promise<BenchmarkMarketDataDetails> {
const marketData: { date: string; value: number }[] = [];
const userCurrency = user.Settings.settings.baseCurrency;
const userId = user.id;
const { chart } = await this.portfolioService.getPerformance({
dateRange,
filters,
impersonationId,
userId,
withExcludedAccounts
});
const [currentSymbolItem, marketDataItems] = await Promise.all([
this.symbolService.get({
dataGatheringItem: {
dataSource,
symbol
}
}),
this.marketDataService.marketDataItems({
orderBy: {
date: 'asc'
},
where: {
dataSource,
symbol,
date: {
in: chart.map(({ date }) => {
return resetHours(parseDate(date));
})
}
}
})
]);
const exchangeRates =
await this.exchangeRateDataService.getExchangeRatesByCurrency({
startDate,
currencies: [currentSymbolItem.currency],
targetCurrency: userCurrency
});
const exchangeRateAtStartDate =
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
format(startDate, DATE_FORMAT)
];
const marketPriceAtStartDate = marketDataItems?.find(({ date }) => {
return isSameDay(date, startDate);
})?.marketPrice;
if (!marketPriceAtStartDate) {
Logger.error(
`No historical market data has been found for ${symbol} (${dataSource}) at ${format(
startDate,
DATE_FORMAT
)}`,
'BenchmarkService'
);
return { marketData };
}
for (const marketDataItem of marketDataItems) {
const exchangeRate =
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
format(marketDataItem.date, DATE_FORMAT)
];
const exchangeRateFactor =
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
? exchangeRate / exchangeRateAtStartDate
: 1;
marketData.push({
date: format(marketDataItem.date, DATE_FORMAT),
value:
marketPriceAtStartDate === 0
? 0
: this.benchmarkService.calculateChangeInPercentage(
marketPriceAtStartDate,
marketDataItem.marketPrice * exchangeRateFactor
) * 100
});
}
const includesEndDate = isSameDay(
parseDate(marketData.at(-1).date),
endDate
);
if (currentSymbolItem?.marketPrice && !includesEndDate) {
const exchangeRate =
exchangeRates[`${currentSymbolItem.currency}${userCurrency}`]?.[
format(endDate, DATE_FORMAT)
];
const exchangeRateFactor =
isNumber(exchangeRateAtStartDate) && isNumber(exchangeRate)
? exchangeRate / exchangeRateAtStartDate
: 1;
marketData.push({
date: format(endDate, DATE_FORMAT),
value:
this.benchmarkService.calculateChangeInPercentage(
marketPriceAtStartDate,
currentSymbolItem.marketPrice * exchangeRateFactor
) * 100
});
}
return {
marketData
};
}
}

View File

@ -0,0 +1,15 @@
import { Granularity } from '@ghostfolio/common/types';
import { IsIn, IsISO8601, IsOptional } from 'class-validator';
export class GetDividendsDto {
@IsISO8601()
from: string;
@IsIn(['day', 'month'] as Granularity[])
@IsOptional()
granularity: Granularity;
@IsISO8601()
to: string;
}

View File

@ -0,0 +1,15 @@
import { Granularity } from '@ghostfolio/common/types';
import { IsIn, IsISO8601, IsOptional } from 'class-validator';
export class GetHistoricalDto {
@IsISO8601()
from: string;
@IsIn(['day', 'month'] as Granularity[])
@IsOptional()
granularity: Granularity;
@IsISO8601()
to: string;
}

View File

@ -0,0 +1,10 @@
import { Transform } from 'class-transformer';
import { IsString } from 'class-validator';
export class GetQuotesDto {
@IsString({ each: true })
@Transform(({ value }) =>
typeof value === 'string' ? value.split(',') : value
)
symbols: string[];
}

View File

@ -0,0 +1,241 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { parseDate } from '@ghostfolio/common/helper';
import {
DataProviderGhostfolioAssetProfileResponse,
DataProviderGhostfolioStatusResponse,
DividendsResponse,
HistoricalResponse,
LookupResponse,
QuotesResponse
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Get,
HttpException,
Inject,
Param,
Query,
UseGuards,
Version
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { isISIN } from 'class-validator';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import { GetDividendsDto } from './get-dividends.dto';
import { GetHistoricalDto } from './get-historical.dto';
import { GetQuotesDto } from './get-quotes.dto';
import { GhostfolioService } from './ghostfolio.service';
@Controller('data-providers/ghostfolio')
export class GhostfolioController {
public constructor(
private readonly ghostfolioService: GhostfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get('asset-profile/:symbol')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('api-key'), HasPermissionGuard)
public async getAssetProfile(
@Param('symbol') symbol: string
): Promise<DataProviderGhostfolioAssetProfileResponse> {
const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests();
if (
this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
try {
const assetProfile = await this.ghostfolioService.getAssetProfile({
symbol
});
await this.ghostfolioService.incrementDailyRequests({
userId: this.request.user.id
});
return assetProfile;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get('dividends/:symbol')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('api-key'), HasPermissionGuard)
@Version('2')
public async getDividends(
@Param('symbol') symbol: string,
@Query() query: GetDividendsDto
): Promise<DividendsResponse> {
const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests();
if (
this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
try {
const dividends = await this.ghostfolioService.getDividends({
symbol,
from: parseDate(query.from),
granularity: query.granularity,
to: parseDate(query.to)
});
await this.ghostfolioService.incrementDailyRequests({
userId: this.request.user.id
});
return dividends;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get('historical/:symbol')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('api-key'), HasPermissionGuard)
@Version('2')
public async getHistorical(
@Param('symbol') symbol: string,
@Query() query: GetHistoricalDto
): Promise<HistoricalResponse> {
const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests();
if (
this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
try {
const historicalData = await this.ghostfolioService.getHistorical({
symbol,
from: parseDate(query.from),
granularity: query.granularity,
to: parseDate(query.to)
});
await this.ghostfolioService.incrementDailyRequests({
userId: this.request.user.id
});
return historicalData;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get('lookup')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('api-key'), HasPermissionGuard)
@Version('2')
public async lookupSymbol(
@Query('includeIndices') includeIndicesParam = 'false',
@Query('query') query = ''
): Promise<LookupResponse> {
const includeIndices = includeIndicesParam === 'true';
const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests();
if (
this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
try {
const result = await this.ghostfolioService.lookup({
includeIndices,
query: isISIN(query.toUpperCase())
? query.toUpperCase()
: query.toLowerCase()
});
await this.ghostfolioService.incrementDailyRequests({
userId: this.request.user.id
});
return result;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get('quotes')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('api-key'), HasPermissionGuard)
@Version('2')
public async getQuotes(
@Query() query: GetQuotesDto
): Promise<QuotesResponse> {
const maxDailyRequests = await this.ghostfolioService.getMaxDailyRequests();
if (
this.request.user.dataProviderGhostfolioDailyRequests > maxDailyRequests
) {
throw new HttpException(
getReasonPhrase(StatusCodes.TOO_MANY_REQUESTS),
StatusCodes.TOO_MANY_REQUESTS
);
}
try {
const quotes = await this.ghostfolioService.getQuotes({
symbols: query.symbols
});
await this.ghostfolioService.incrementDailyRequests({
userId: this.request.user.id
});
return quotes;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get('status')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('api-key'), HasPermissionGuard)
@Version('2')
public async getStatus(): Promise<DataProviderGhostfolioStatusResponse> {
return this.ghostfolioService.getStatus({ user: this.request.user });
}
}

View File

@ -0,0 +1,83 @@
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { CryptocurrencyModule } from '@ghostfolio/api/services/cryptocurrency/cryptocurrency.module';
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
import { CoinGeckoService } from '@ghostfolio/api/services/data-provider/coingecko/coingecko.service';
import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider/eod-historical-data/eod-historical-data.service';
import { FinancialModelingPrepService } from '@ghostfolio/api/services/data-provider/financial-modeling-prep/financial-modeling-prep.service';
import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/google-sheets/google-sheets.service';
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service';
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
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 { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { GhostfolioController } from './ghostfolio.controller';
import { GhostfolioService } from './ghostfolio.service';
@Module({
controllers: [GhostfolioController],
imports: [
CryptocurrencyModule,
DataProviderModule,
MarketDataModule,
PrismaModule,
PropertyModule,
RedisCacheModule,
SymbolProfileModule
],
providers: [
AlphaVantageService,
CoinGeckoService,
ConfigurationService,
DataProviderService,
EodHistoricalDataService,
FinancialModelingPrepService,
GhostfolioService,
GoogleSheetsService,
ManualService,
RapidApiService,
YahooFinanceService,
YahooFinanceDataEnhancerService,
{
inject: [
AlphaVantageService,
CoinGeckoService,
EodHistoricalDataService,
FinancialModelingPrepService,
GoogleSheetsService,
ManualService,
RapidApiService,
YahooFinanceService
],
provide: 'DataProviderInterfaces',
useFactory: (
alphaVantageService,
coinGeckoService,
eodHistoricalDataService,
financialModelingPrepService,
googleSheetsService,
manualService,
rapidApiService,
yahooFinanceService
) => [
alphaVantageService,
coinGeckoService,
eodHistoricalDataService,
financialModelingPrepService,
googleSheetsService,
manualService,
rapidApiService,
yahooFinanceService
]
}
]
})
export class GhostfolioModule {}

View File

@ -0,0 +1,350 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { GhostfolioService as GhostfolioDataProviderService } from '@ghostfolio/api/services/data-provider/ghostfolio/ghostfolio.service';
import {
GetAssetProfileParams,
GetDividendsParams,
GetHistoricalParams,
GetQuotesParams,
GetSearchParams
} from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import {
DEFAULT_CURRENCY,
DERIVED_CURRENCIES
} from '@ghostfolio/common/config';
import { PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS } from '@ghostfolio/common/config';
import {
DataProviderGhostfolioAssetProfileResponse,
DataProviderInfo,
DividendsResponse,
HistoricalResponse,
LookupItem,
LookupResponse,
QuotesResponse
} from '@ghostfolio/common/interfaces';
import { UserWithSettings } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { DataSource, SymbolProfile } from '@prisma/client';
import { Big } from 'big.js';
@Injectable()
export class GhostfolioService {
public constructor(
private readonly configurationService: ConfigurationService,
private readonly dataProviderService: DataProviderService,
private readonly prismaService: PrismaService,
private readonly propertyService: PropertyService
) {}
public async getAssetProfile({
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
symbol
}: GetAssetProfileParams) {
let result: DataProviderGhostfolioAssetProfileResponse = {};
try {
const promises: Promise<Partial<SymbolProfile>>[] = [];
for (const dataProviderService of this.getDataProviderServices()) {
promises.push(
dataProviderService
.getAssetProfile({
requestTimeout,
symbol
})
.then((assetProfile) => {
result = {
...result,
...assetProfile,
dataSource: DataSource.GHOSTFOLIO
};
return assetProfile;
})
);
}
await Promise.all(promises);
return result;
} catch (error) {
Logger.error(error, 'GhostfolioService');
throw error;
}
}
public async getDividends({
from,
granularity,
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
symbol,
to
}: GetDividendsParams) {
const result: DividendsResponse = { dividends: {} };
try {
const promises: Promise<{
[date: string]: IDataProviderHistoricalResponse;
}>[] = [];
for (const dataProviderService of this.getDataProviderServices()) {
promises.push(
dataProviderService
.getDividends({
from,
granularity,
requestTimeout,
symbol,
to
})
.then((dividends) => {
result.dividends = dividends;
return dividends;
})
);
}
await Promise.all(promises);
return result;
} catch (error) {
Logger.error(error, 'GhostfolioService');
throw error;
}
}
public async getHistorical({
from,
granularity,
requestTimeout,
to,
symbol
}: GetHistoricalParams) {
const result: HistoricalResponse = { historicalData: {} };
try {
const promises: Promise<{
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}>[] = [];
for (const dataProviderService of this.getDataProviderServices()) {
promises.push(
dataProviderService
.getHistorical({
from,
granularity,
requestTimeout,
symbol,
to
})
.then((historicalData) => {
result.historicalData = historicalData[symbol];
return historicalData;
})
);
}
await Promise.all(promises);
return result;
} catch (error) {
Logger.error(error, 'GhostfolioService');
throw error;
}
}
public async getMaxDailyRequests() {
return parseInt(
(await this.propertyService.getByKey<string>(
PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS
)) || '0',
10
);
}
public async getQuotes({ requestTimeout, symbols }: GetQuotesParams) {
const results: QuotesResponse = { quotes: {} };
try {
const promises: Promise<any>[] = [];
for (const dataProvider of this.getDataProviderServices()) {
const maximumNumberOfSymbolsPerRequest =
dataProvider.getMaxNumberOfSymbolsPerRequest?.() ??
Number.MAX_SAFE_INTEGER;
for (
let i = 0;
i < symbols.length;
i += maximumNumberOfSymbolsPerRequest
) {
const symbolsChunk = symbols.slice(
i,
i + maximumNumberOfSymbolsPerRequest
);
const promise = Promise.resolve(
dataProvider.getQuotes({ requestTimeout, symbols: symbolsChunk })
);
promises.push(
promise.then(async (result) => {
for (const [symbol, dataProviderResponse] of Object.entries(
result
)) {
dataProviderResponse.dataSource = 'GHOSTFOLIO';
if (
[
...DERIVED_CURRENCIES.map(({ currency }) => {
return `${DEFAULT_CURRENCY}${currency}`;
}),
`${DEFAULT_CURRENCY}USX`
].includes(symbol)
) {
continue;
}
results.quotes[symbol] = dataProviderResponse;
for (const {
currency,
factor,
rootCurrency
} of DERIVED_CURRENCIES) {
if (symbol === `${DEFAULT_CURRENCY}${rootCurrency}`) {
results.quotes[`${DEFAULT_CURRENCY}${currency}`] = {
...dataProviderResponse,
currency,
marketPrice: new Big(
result[`${DEFAULT_CURRENCY}${rootCurrency}`].marketPrice
)
.mul(factor)
.toNumber(),
marketState: 'open'
};
}
}
}
})
);
}
await Promise.all(promises);
}
return results;
} catch (error) {
Logger.error(error, 'GhostfolioService');
throw error;
}
}
public async getStatus({ user }: { user: UserWithSettings }) {
return {
dailyRequests: user.dataProviderGhostfolioDailyRequests,
dailyRequestsMax: await this.getMaxDailyRequests(),
subscription: user.subscription
};
}
public async incrementDailyRequests({ userId }: { userId: string }) {
await this.prismaService.analytics.update({
data: {
dataProviderGhostfolioDailyRequests: { increment: 1 }
},
where: { userId }
});
}
public async lookup({
includeIndices = false,
query
}: GetSearchParams): Promise<LookupResponse> {
const results: LookupResponse = { items: [] };
if (!query) {
return results;
}
try {
let lookupItems: LookupItem[] = [];
const promises: Promise<{ items: LookupItem[] }>[] = [];
if (query?.length < 2) {
return { items: lookupItems };
}
for (const dataProviderService of this.getDataProviderServices()) {
promises.push(
dataProviderService.search({
includeIndices,
query
})
);
}
const searchResults = await Promise.all(promises);
for (const { items } of searchResults) {
if (items?.length > 0) {
lookupItems = lookupItems.concat(items);
}
}
const filteredItems = lookupItems
.filter(({ currency }) => {
// Only allow symbols with supported currency
return currency ? true : false;
})
.sort(({ name: name1 }, { name: name2 }) => {
return name1?.toLowerCase().localeCompare(name2?.toLowerCase());
})
.map((lookupItem) => {
lookupItem.dataProviderInfo = this.getDataProviderInfo();
lookupItem.dataSource = 'GHOSTFOLIO';
return lookupItem;
});
results.items = filteredItems;
return results;
} catch (error) {
Logger.error(error, 'GhostfolioService');
throw error;
}
}
private getDataProviderInfo(): DataProviderInfo {
const ghostfolioDataProviderService = new GhostfolioDataProviderService(
this.configurationService,
this.propertyService
);
return {
...ghostfolioDataProviderService.getDataProviderInfo(),
isPremium: false,
name: 'Ghostfolio Premium'
};
}
private getDataProviderServices() {
return this.configurationService
.get('DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER')
.map((dataSource) => {
return this.dataProviderService.getDataProvider(DataSource[dataSource]);
});
}
}

View File

@ -0,0 +1,137 @@
import { AdminService } from '@ghostfolio/api/app/admin/admin.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { getCurrencyFromSymbol, isCurrency } from '@ghostfolio/common/helper';
import { MarketDataDetailsResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
Get,
HttpException,
Inject,
Param,
Post,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { DataSource, Prisma } from '@prisma/client';
import { parseISO } from 'date-fns';
import { getReasonPhrase, StatusCodes } from 'http-status-codes';
import { UpdateBulkMarketDataDto } from './update-bulk-market-data.dto';
@Controller('market-data')
export class MarketDataController {
public constructor(
private readonly adminService: AdminService,
private readonly marketDataService: MarketDataService,
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly symbolProfileService: SymbolProfileService
) {}
@Get(':dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
public async getMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<MarketDataDetailsResponse> {
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([
{ dataSource, symbol }
]);
if (!assetProfile && !isCurrency(getCurrencyFromSymbol(symbol))) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
const canReadAllAssetProfiles = hasPermission(
this.request.user.permissions,
permissions.readMarketData
);
const canReadOwnAssetProfile =
assetProfile?.userId === this.request.user.id &&
hasPermission(
this.request.user.permissions,
permissions.readMarketDataOfOwnAssetProfile
);
if (!canReadAllAssetProfiles && !canReadOwnAssetProfile) {
throw new HttpException(
assetProfile.userId
? getReasonPhrase(StatusCodes.NOT_FOUND)
: getReasonPhrase(StatusCodes.FORBIDDEN),
assetProfile.userId ? StatusCodes.NOT_FOUND : StatusCodes.FORBIDDEN
);
}
return this.adminService.getMarketDataBySymbol({ dataSource, symbol });
}
@Post(':dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
public async updateMarketData(
@Body() data: UpdateBulkMarketDataDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
const [assetProfile] = await this.symbolProfileService.getSymbolProfiles([
{ dataSource, symbol }
]);
if (!assetProfile && !isCurrency(getCurrencyFromSymbol(symbol))) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
const canUpsertAllAssetProfiles =
hasPermission(
this.request.user.permissions,
permissions.createMarketData
) &&
hasPermission(
this.request.user.permissions,
permissions.updateMarketData
);
const canUpsertOwnAssetProfile =
assetProfile?.userId === this.request.user.id &&
hasPermission(
this.request.user.permissions,
permissions.createMarketDataOfOwnAssetProfile
) &&
hasPermission(
this.request.user.permissions,
permissions.updateMarketDataOfOwnAssetProfile
);
if (!canUpsertAllAssetProfiles && !canUpsertOwnAssetProfile) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map(
({ date, marketPrice }) => ({
dataSource,
marketPrice,
symbol,
date: parseISO(date),
state: 'CLOSE'
})
);
return this.marketDataService.updateMany({
data: dataBulkUpdate
});
}
}

View File

@ -0,0 +1,13 @@
import { AdminModule } from '@ghostfolio/api/app/admin/admin.module';
import { MarketDataModule as MarketDataServiceModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { MarketDataController } from './market-data.controller';
@Module({
controllers: [MarketDataController],
imports: [AdminModule, MarketDataServiceModule, SymbolProfileModule]
})
export class MarketDataModule {}

View File

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

View File

@ -0,0 +1,140 @@
import { AccessService } from '@ghostfolio/api/app/access/access.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
import { getSum } from '@ghostfolio/common/helper';
import { PublicPortfolioResponse } from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Get,
HttpException,
Inject,
Param,
UseInterceptors
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Big } from 'big.js';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Controller('public')
export class PublicController {
public constructor(
private readonly accessService: AccessService,
private readonly configurationService: ConfigurationService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly portfolioService: PortfolioService,
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly userService: UserService
) {}
@Get(':accessId/portfolio')
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getPublicPortfolio(
@Param('accessId') accessId
): Promise<PublicPortfolioResponse> {
const access = await this.accessService.access({ id: accessId });
if (!access) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
let hasDetails = true;
const user = await this.userService.user({
id: access.userId
});
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
hasDetails = user.subscription.type === 'Premium';
}
const [
{ createdAt, holdings, markets },
{ performance: performance1d },
{ performance: performanceMax },
{ performance: performanceYtd }
] = await Promise.all([
this.portfolioService.getDetails({
impersonationId: access.userId,
userId: user.id,
withMarkets: true
}),
...['1d', 'max', 'ytd'].map((dateRange) => {
return this.portfolioService.getPerformance({
dateRange,
impersonationId: undefined,
userId: user.id
});
})
]);
Object.values(markets ?? {}).forEach((market) => {
delete market.valueInBaseCurrency;
});
const publicPortfolioResponse: PublicPortfolioResponse = {
createdAt,
hasDetails,
markets,
alias: access.alias,
holdings: {},
performance: {
'1d': {
relativeChange:
performance1d.netPerformancePercentageWithCurrencyEffect
},
max: {
relativeChange:
performanceMax.netPerformancePercentageWithCurrencyEffect
},
ytd: {
relativeChange:
performanceYtd.netPerformancePercentageWithCurrencyEffect
}
}
};
const totalValue = getSum(
Object.values(holdings).map(({ currency, marketPrice, quantity }) => {
return new Big(
this.exchangeRateDataService.toCurrency(
quantity * marketPrice,
currency,
this.request.user?.Settings?.settings.baseCurrency ??
DEFAULT_CURRENCY
)
);
})
).toNumber();
for (const [symbol, portfolioPosition] of Object.entries(holdings)) {
publicPortfolioResponse.holdings[symbol] = {
allocationInPercentage:
portfolioPosition.valueInBaseCurrency / totalValue,
assetClass: hasDetails ? portfolioPosition.assetClass : undefined,
countries: hasDetails ? portfolioPosition.countries : [],
currency: hasDetails ? portfolioPosition.currency : undefined,
dataSource: portfolioPosition.dataSource,
dateOfFirstActivity: portfolioPosition.dateOfFirstActivity,
markets: hasDetails ? portfolioPosition.markets : undefined,
name: portfolioPosition.name,
netPerformancePercentWithCurrencyEffect:
portfolioPosition.netPerformancePercentWithCurrencyEffect,
sectors: hasDetails ? portfolioPosition.sectors : [],
symbol: portfolioPosition.symbol,
url: portfolioPosition.url,
valueInPercentage: portfolioPosition.valueInBaseCurrency / totalValue
};
}
return publicPortfolioResponse;
}
}

View File

@ -0,0 +1,53 @@
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 { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { PortfolioCalculatorFactory } from '@ghostfolio/api/app/portfolio/calculator/portfolio-calculator.factory';
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';
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 { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
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 { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { PublicController } from './public.controller';
@Module({
controllers: [PublicController],
imports: [
AccessModule,
BenchmarkModule,
DataProviderModule,
ExchangeRateDataModule,
I18nModule,
ImpersonationModule,
MarketDataModule,
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule,
TransformDataSourceInRequestModule,
UserModule
],
providers: [
AccountBalanceService,
AccountService,
CurrentRateService,
PortfolioCalculatorFactory,
PortfolioService,
RulesService
]
})
export class PublicModule {}

View File

@ -0,0 +1,51 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import {
DATE_FORMAT,
getYesterday,
interpolate
} from '@ghostfolio/common/helper';
import { Controller, Get, Res, VERSION_NEUTRAL, Version } from '@nestjs/common';
import { format } from 'date-fns';
import { Response } from 'express';
import { readFileSync } from 'fs';
import { join } from 'path';
import { SitemapService } from './sitemap.service';
@Controller('sitemap.xml')
export class SitemapController {
public sitemapXml = '';
public constructor(
private readonly configurationService: ConfigurationService,
private readonly sitemapService: SitemapService
) {
try {
this.sitemapXml = readFileSync(
join(__dirname, 'assets', 'sitemap.xml'),
'utf8'
);
} catch {}
}
@Get()
@Version(VERSION_NEUTRAL)
public getSitemapXml(@Res() response: Response) {
const currentDate = format(getYesterday(), DATE_FORMAT);
response.setHeader('content-type', 'application/xml');
response.send(
interpolate(this.sitemapXml, {
personalFinanceTools: this.configurationService.get(
'ENABLE_FEATURE_SUBSCRIPTION'
)
? this.sitemapService.getPersonalFinanceTools({ currentDate })
: '',
publicRoutes: this.sitemapService.getPublicRoutes({
currentDate
})
})
);
}
}

View File

@ -0,0 +1,14 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { I18nModule } from '@ghostfolio/api/services/i18n/i18n.module';
import { Module } from '@nestjs/common';
import { SitemapController } from './sitemap.controller';
import { SitemapService } from './sitemap.service';
@Module({
controllers: [SitemapController],
imports: [ConfigurationModule, I18nModule],
providers: [SitemapService]
})
export class SitemapModule {}

View File

@ -0,0 +1,116 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
import { SUPPORTED_LANGUAGE_CODES } from '@ghostfolio/common/config';
import { personalFinanceTools } from '@ghostfolio/common/personal-finance-tools';
import { PublicRoute } from '@ghostfolio/common/routes/interfaces/public-route.interface';
import { publicRoutes } from '@ghostfolio/common/routes/routes';
import { Injectable } from '@nestjs/common';
@Injectable()
export class SitemapService {
private static readonly TRANSLATION_TAGGED_MESSAGE_REGEX =
/:.*@@(?<id>[a-zA-Z0-9.]+):(?<message>.+)/;
public constructor(
private readonly configurationService: ConfigurationService,
private readonly i18nService: I18nService
) {}
public getPersonalFinanceTools({ currentDate }: { currentDate: string }) {
const rootUrl = this.configurationService.get('ROOT_URL');
return SUPPORTED_LANGUAGE_CODES.flatMap((languageCode) => {
return personalFinanceTools.map(({ alias, key }) => {
const route =
publicRoutes.resources.subRoutes.personalFinanceTools.subRoutes
.product;
const params = {
currentDate,
languageCode,
rootUrl,
urlPostfix: alias ?? key
};
return this.createRouteSitemapUrl({ ...params, route });
});
}).join('\n');
}
public getPublicRoutes({ currentDate }: { currentDate: string }) {
const rootUrl = this.configurationService.get('ROOT_URL');
return SUPPORTED_LANGUAGE_CODES.flatMap((languageCode) => {
const params = {
currentDate,
languageCode,
rootUrl
};
return [
this.createRouteSitemapUrl(params),
...this.createSitemapUrls(params, publicRoutes)
];
}).join('\n');
}
private createRouteSitemapUrl({
currentDate,
languageCode,
rootUrl,
route,
urlPostfix
}: {
currentDate: string;
languageCode: string;
rootUrl: string;
route?: PublicRoute;
urlPostfix?: string;
}): string {
const segments =
route?.routerLink.map((link) => {
const match = link.match(
SitemapService.TRANSLATION_TAGGED_MESSAGE_REGEX
);
const segment = match
? (this.i18nService.getTranslation({
languageCode,
id: match.groups.id
}) ?? match.groups.message)
: link;
return segment.replace(/^\/+|\/+$/, '');
}) ?? [];
const location =
[rootUrl, languageCode, ...segments].join('/') +
(urlPostfix ? `-${urlPostfix}` : '');
return [
' <url>',
` <loc>${location}</loc>`,
` <lastmod>${currentDate}T00:00:00+00:00</lastmod>`,
' </url>'
].join('\n');
}
private createSitemapUrls(
params: { currentDate: string; languageCode: string; rootUrl: string },
routes: Record<string, PublicRoute>
): string[] {
return Object.values(routes).flatMap((route) => {
if (route.excludeFromSitemap) {
return [];
}
const urls = [this.createRouteSitemapUrl({ ...params, route })];
if (route.subRoutes) {
urls.push(...this.createSitemapUrls(params, route.subRoutes));
}
return urls;
});
}
}

View File

@ -0,0 +1,10 @@
import { IsOptional, IsString } from 'class-validator';
export class CreateTagDto {
@IsString()
name: string;
@IsOptional()
@IsString()
userId?: string;
}

View File

@ -1,6 +1,8 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { permissions } from '@ghostfolio/common/permissions';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
@ -8,36 +10,84 @@ import {
Delete,
Get,
HttpException,
Inject,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Tag } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateTagDto } from './create-tag.dto';
import { TagService } from './tag.service';
import { UpdateTagDto } from './update-tag.dto';
@Controller('tag')
export class TagController {
public constructor(private readonly tagService: TagService) {}
@Controller('tags')
export class TagsController {
public constructor(
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly tagService: TagService
) {}
@Post()
@UseGuards(AuthGuard('jwt'))
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
const canCreateOwnTag = hasPermission(
this.request.user.permissions,
permissions.createOwnTag
);
const canCreateTag = hasPermission(
this.request.user.permissions,
permissions.createTag
);
if (!canCreateOwnTag && !canCreateTag) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
if (canCreateOwnTag && !canCreateTag) {
if (data.userId !== this.request.user.id) {
throw new HttpException(
getReasonPhrase(StatusCodes.BAD_REQUEST),
StatusCodes.BAD_REQUEST
);
}
}
return this.tagService.createTag(data);
}
@Delete(':id')
@HasPermission(permissions.deleteTag)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteTag(@Param('id') id: string) {
const originalTag = await this.tagService.getTag({
id
});
if (!originalTag) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.tagService.deleteTag({ id });
}
@Get()
@HasPermission(permissions.readTags)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getTags() {
return this.tagService.getTagsWithActivityCount();
}
@Post()
@HasPermission(permissions.createTag)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
return this.tagService.createTag(data);
}
@HasPermission(permissions.updateTag)
@Put(':id')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@ -62,22 +112,4 @@ export class TagController {
}
});
}
@Delete(':id')
@HasPermission(permissions.deleteTag)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteTag(@Param('id') id: string) {
const originalTag = await this.tagService.getTag({
id
});
if (!originalTag) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.tagService.deleteTag({ id });
}
}

View File

@ -0,0 +1,12 @@
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common';
import { TagsController } from './tags.controller';
@Module({
controllers: [TagsController],
imports: [PrismaModule, TagModule]
})
export class TagsModule {}

View File

@ -0,0 +1,13 @@
import { IsOptional, IsString } from 'class-validator';
export class UpdateTagDto {
@IsString()
id: string;
@IsString()
name: string;
@IsOptional()
@IsString()
userId?: string;
}

View File

@ -0,0 +1,10 @@
import { DataSource } from '@prisma/client';
import { IsEnum, IsString } from 'class-validator';
export class CreateWatchlistItemDto {
@IsEnum(DataSource)
dataSource: DataSource;
@IsString()
symbol: string;
}

View File

@ -0,0 +1,100 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { WatchlistResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
Delete,
Get,
Headers,
HttpException,
Inject,
Param,
Post,
UseGuards,
UseInterceptors
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { DataSource } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateWatchlistItemDto } from './create-watchlist-item.dto';
import { WatchlistService } from './watchlist.service';
@Controller('watchlist')
export class WatchlistController {
public constructor(
private readonly impersonationService: ImpersonationService,
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly watchlistService: WatchlistService
) {}
@Post()
@HasPermission(permissions.createWatchlistItem)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async createWatchlistItem(@Body() data: CreateWatchlistItemDto) {
return this.watchlistService.createWatchlistItem({
dataSource: data.dataSource,
symbol: data.symbol,
userId: this.request.user.id
});
}
@Delete(':dataSource/:symbol')
@HasPermission(permissions.deleteWatchlistItem)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async deleteWatchlistItem(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
const watchlistItems = await this.watchlistService.getWatchlistItems(
this.request.user.id
);
const watchlistItem = watchlistItems.find((item) => {
return item.dataSource === dataSource && item.symbol === symbol;
});
if (!watchlistItem) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return this.watchlistService.deleteWatchlistItem({
dataSource,
symbol,
userId: this.request.user.id
});
}
@Get()
@HasPermission(permissions.readWatchlist)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getWatchlistItems(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string
): Promise<WatchlistResponse> {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId);
const watchlist = await this.watchlistService.getWatchlistItems(
impersonationUserId || this.request.user.id
);
return {
watchlist
};
}
}

View File

@ -0,0 +1,31 @@
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 { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation/impersonation.module';
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
import { WatchlistController } from './watchlist.controller';
import { WatchlistService } from './watchlist.service';
@Module({
controllers: [WatchlistController],
imports: [
BenchmarkModule,
DataGatheringModule,
DataProviderModule,
ImpersonationModule,
MarketDataModule,
PrismaModule,
SymbolProfileModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
],
providers: [WatchlistService]
})
export class WatchlistModule {}

View File

@ -0,0 +1,150 @@
import { BenchmarkService } from '@ghostfolio/api/services/benchmark/benchmark.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 { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { WatchlistResponse } from '@ghostfolio/common/interfaces';
import { BadRequestException, Injectable } from '@nestjs/common';
import { DataSource, Prisma } from '@prisma/client';
@Injectable()
export class WatchlistService {
public constructor(
private readonly benchmarkService: BenchmarkService,
private readonly dataGatheringService: DataGatheringService,
private readonly dataProviderService: DataProviderService,
private readonly marketDataService: MarketDataService,
private readonly prismaService: PrismaService,
private readonly symbolProfileService: SymbolProfileService
) {}
public async createWatchlistItem({
dataSource,
symbol,
userId
}: {
dataSource: DataSource;
symbol: string;
userId: string;
}): Promise<void> {
const symbolProfile = await this.prismaService.symbolProfile.findUnique({
where: {
dataSource_symbol: { dataSource, symbol }
}
});
if (!symbolProfile) {
const assetProfiles = await this.dataProviderService.getAssetProfiles([
{ dataSource, symbol }
]);
if (!assetProfiles[symbol]?.currency) {
throw new BadRequestException(
`Asset profile not found for ${symbol} (${dataSource})`
);
}
await this.symbolProfileService.add(
assetProfiles[symbol] as Prisma.SymbolProfileCreateInput
);
}
await this.dataGatheringService.gatherSymbol({
dataSource,
symbol
});
await this.prismaService.user.update({
data: {
watchlist: {
connect: {
dataSource_symbol: { dataSource, symbol }
}
}
},
where: { id: userId }
});
}
public async deleteWatchlistItem({
dataSource,
symbol,
userId
}: {
dataSource: DataSource;
symbol: string;
userId: string;
}) {
await this.prismaService.user.update({
data: {
watchlist: {
disconnect: {
dataSource_symbol: { dataSource, symbol }
}
}
},
where: { id: userId }
});
}
public async getWatchlistItems(
userId: string
): Promise<WatchlistResponse['watchlist']> {
const user = await this.prismaService.user.findUnique({
select: {
watchlist: {
select: { dataSource: true, symbol: true }
}
},
where: { id: userId }
});
const [assetProfiles, quotes] = await Promise.all([
this.symbolProfileService.getSymbolProfiles(user.watchlist),
this.dataProviderService.getQuotes({
items: user.watchlist.map(({ dataSource, symbol }) => {
return { dataSource, symbol };
})
})
]);
const watchlist = await Promise.all(
user.watchlist.map(async ({ dataSource, symbol }) => {
const assetProfile = assetProfiles.find((profile) => {
return profile.dataSource === dataSource && profile.symbol === symbol;
});
const allTimeHigh = await this.marketDataService.getMax({
dataSource,
symbol
});
const performancePercent =
this.benchmarkService.calculateChangeInPercentage(
allTimeHigh?.marketPrice,
quotes[symbol]?.marketPrice
);
return {
dataSource,
symbol,
marketCondition:
this.benchmarkService.getMarketCondition(performancePercent),
name: assetProfile?.name,
performances: {
allTimeHigh: {
performancePercent,
date: allTimeHigh?.date
}
}
};
})
);
return watchlist.sort((a, b) => {
return a.name.localeCompare(b.name);
});
}
}

View File

@ -1,9 +1,17 @@
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { Export } from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Get, Inject, Query, UseGuards } from '@nestjs/common';
import {
Controller,
Get,
Inject,
Query,
UseGuards,
UseInterceptors
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
@ -19,15 +27,21 @@ export class ExportController {
@Get()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async export(
@Query('accounts') filterByAccounts?: string,
@Query('activityIds') activityIds?: string[],
@Query('activityIds') filterByActivityIds?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string,
@Query('tags') filterByTags?: string
): Promise<Export> {
const activityIds = filterByActivityIds?.split(',') ?? [];
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByDataSource,
filterBySymbol,
filterByTags
});

View File

@ -1,10 +1,8 @@
import { AccountModule } from '@ghostfolio/api/app/account/account.module';
import { OrderModule } from '@ghostfolio/api/app/order/order.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 { ApiModule } from '@ghostfolio/api/services/api/api.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common';
@ -12,16 +10,14 @@ import { ExportController } from './export.controller';
import { ExportService } from './export.service';
@Module({
controllers: [ExportController],
imports: [
AccountModule,
ApiModule,
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
OrderModule,
RedisCacheModule
TagModule,
TransformDataSourceInRequestModule
],
controllers: [ExportController],
providers: [ExportService]
})
export class ExportModule {}

View File

@ -1,15 +1,18 @@
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { environment } from '@ghostfolio/api/environments/environment';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { Filter, Export } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { Platform } from '@prisma/client';
@Injectable()
export class ExportService {
public constructor(
private readonly accountService: AccountService,
private readonly orderService: OrderService
private readonly orderService: OrderService,
private readonly tagService: TagService
) {}
public async export({
@ -23,26 +26,7 @@ export class ExportService {
userCurrency: string;
userId: string;
}): Promise<Export> {
const accounts = (
await this.accountService.accounts({
orderBy: {
name: 'asc'
},
where: { userId }
})
).map(
({ balance, comment, currency, id, isExcluded, name, platformId }) => {
return {
balance,
comment,
currency,
id,
isExcluded,
name,
platformId
};
}
);
const platformsMap: { [platformId: string]: Platform } = {};
let { activities } = await this.orderService.getOrders({
filters,
@ -54,24 +38,95 @@ export class ExportService {
withExcludedAccounts: true
});
if (activityIds) {
activities = activities.filter((activity) => {
return activityIds.includes(activity.id);
if (activityIds?.length > 0) {
activities = activities.filter(({ id }) => {
return activityIds.includes(id);
});
}
const accounts = (
await this.accountService.accounts({
include: {
balances: true,
platform: true
},
orderBy: {
name: 'asc'
},
where: { userId }
})
)
.filter(({ id }) => {
return activities.length > 0
? activities.some(({ accountId }) => {
return accountId === id;
})
: true;
})
.map(
({
balance,
balances,
comment,
currency,
id,
isExcluded,
name,
platform,
platformId
}) => {
if (platformId) {
platformsMap[platformId] = platform;
}
return {
balance,
balances: balances.map(({ date, value }) => {
return { date: date.toISOString(), value };
}),
comment,
currency,
id,
isExcluded,
name,
platformId
};
}
);
const tags = (await this.tagService.getTagsForUser(userId))
.filter(
({ id, isUsed }) =>
isUsed &&
activities.some((activity) => {
return activity.tags.some(({ id: tagId }) => {
return tagId === id;
});
})
)
.map(({ id, name }) => {
return {
id,
name
};
});
return {
meta: { date: new Date().toISOString(), version: environment.version },
accounts,
platforms: Object.values(platformsMap),
tags,
activities: activities.map(
({
accountId,
comment,
currency,
date,
fee,
id,
quantity,
SymbolProfile,
tags: currentTags,
type,
unitPrice
}) => {
@ -83,19 +138,21 @@ export class ExportService {
quantity,
type,
unitPrice,
currency: SymbolProfile.currency,
currency: currency ?? SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
date: date.toISOString(),
symbol:
type === 'FEE' ||
type === 'INTEREST' ||
type === 'ITEM' ||
type === 'LIABILITY'
? SymbolProfile.name
: SymbolProfile.symbol
symbol: ['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(type)
? SymbolProfile.name
: SymbolProfile.symbol,
tags: currentTags.map(({ id: tagId }) => {
return tagId;
})
};
}
)
),
user: {
settings: { currency: userCurrency }
}
};
}
}

View File

@ -1,13 +1,20 @@
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import {
DataEnhancerHealthResponse,
DataProviderHealthResponse
} from '@ghostfolio/common/interfaces';
import {
Controller,
Get,
HttpException,
HttpStatus,
Param,
Res,
UseInterceptors
} from '@nestjs/common';
import { DataSource } from '@prisma/client';
import { Response } from 'express';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { HealthService } from './health.service';
@ -17,26 +24,47 @@ export class HealthController {
public constructor(private readonly healthService: HealthService) {}
@Get()
public async getHealth() {}
public async getHealth(@Res() response: Response) {
const databaseServiceHealthy = await this.healthService.isDatabaseHealthy();
const redisCacheServiceHealthy =
await this.healthService.isRedisCacheHealthy();
if (databaseServiceHealthy && redisCacheServiceHealthy) {
return response
.status(HttpStatus.OK)
.json({ status: getReasonPhrase(StatusCodes.OK) });
} else {
return response
.status(HttpStatus.SERVICE_UNAVAILABLE)
.json({ status: getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE) });
}
}
@Get('data-enhancer/:name')
public async getHealthOfDataEnhancer(@Param('name') name: string) {
public async getHealthOfDataEnhancer(
@Param('name') name: string,
@Res() response: Response
): Promise<Response<DataEnhancerHealthResponse>> {
const hasResponse =
await this.healthService.hasResponseFromDataEnhancer(name);
if (hasResponse !== true) {
throw new HttpException(
getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE),
StatusCodes.SERVICE_UNAVAILABLE
);
if (hasResponse) {
return response.status(HttpStatus.OK).json({
status: getReasonPhrase(StatusCodes.OK)
});
} else {
return response
.status(HttpStatus.SERVICE_UNAVAILABLE)
.json({ status: getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE) });
}
}
@Get('data-provider/:dataSource')
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getHealthOfDataProvider(
@Param('dataSource') dataSource: DataSource
) {
@Param('dataSource') dataSource: DataSource,
@Res() response: Response
): Promise<Response<DataProviderHealthResponse>> {
if (!DataSource[dataSource]) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
@ -47,11 +75,14 @@ export class HealthController {
const hasResponse =
await this.healthService.hasResponseFromDataProvider(dataSource);
if (hasResponse !== true) {
throw new HttpException(
getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE),
StatusCodes.SERVICE_UNAVAILABLE
);
if (hasResponse) {
return response
.status(HttpStatus.OK)
.json({ status: getReasonPhrase(StatusCodes.OK) });
} else {
return response
.status(HttpStatus.SERVICE_UNAVAILABLE)
.json({ status: getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE) });
}
}
}

View File

@ -1,6 +1,8 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.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 { Module } from '@nestjs/common';
@ -9,7 +11,13 @@ import { HealthService } from './health.service';
@Module({
controllers: [HealthController],
imports: [ConfigurationModule, DataEnhancerModule, DataProviderModule],
imports: [
DataEnhancerModule,
DataProviderModule,
PropertyModule,
RedisCacheModule,
TransformDataSourceInRequestModule
],
providers: [HealthService]
})
export class HealthModule {}

View File

@ -1,5 +1,8 @@
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 { PROPERTY_CURRENCIES } from '@ghostfolio/common/config';
import { Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
@ -8,7 +11,9 @@ import { DataSource } from '@prisma/client';
export class HealthService {
public constructor(
private readonly dataEnhancerService: DataEnhancerService,
private readonly dataProviderService: DataProviderService
private readonly dataProviderService: DataProviderService,
private readonly propertyService: PropertyService,
private readonly redisCacheService: RedisCacheService
) {}
public async hasResponseFromDataEnhancer(aName: string) {
@ -18,4 +23,24 @@ export class HealthService {
public async hasResponseFromDataProvider(aDataSource: DataSource) {
return this.dataProviderService.checkQuote(aDataSource);
}
public async isDatabaseHealthy() {
try {
await this.propertyService.getByKey(PROPERTY_CURRENCIES);
return true;
} catch {
return false;
}
}
public async isRedisCacheHealthy() {
try {
const isHealthy = await this.redisCacheService.isHealthy();
return isHealthy;
} catch {
return false;
}
}
}

View File

@ -0,0 +1,10 @@
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { AccountBalance } from '@ghostfolio/common/interfaces';
import { IsArray, IsOptional } from 'class-validator';
export class CreateAccountWithBalancesDto extends CreateAccountDto {
@IsArray()
@IsOptional()
balances?: AccountBalance[];
}

View File

@ -1,15 +1,16 @@
import { CreateAccountDto } from '@ghostfolio/api/app/account/create-account.dto';
import { CreateOrderDto } from '@ghostfolio/api/app/order/create-order.dto';
import { Type } from 'class-transformer';
import { IsArray, IsOptional, ValidateNested } from 'class-validator';
import { CreateAccountWithBalancesDto } from './create-account-with-balances.dto';
export class ImportDataDto {
@IsOptional()
@IsArray()
@Type(() => CreateAccountDto)
@Type(() => CreateAccountWithBalancesDto)
@ValidateNested({ each: true })
accounts: CreateAccountDto[];
accounts: CreateAccountWithBalancesDto[];
@IsArray()
@Type(() => CreateOrderDto)

View File

@ -1,7 +1,7 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ImportResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
@ -71,7 +71,7 @@ export class ImportController {
const activities = await this.importService.import({
isDryRun,
maxActivitiesToImport,
accountsDto: importData.accounts ?? [],
accountsWithBalancesDto: importData.accounts ?? [],
activitiesDto: importData.activities,
user: this.request.user
});
@ -98,12 +98,10 @@ export class ImportController {
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<ImportResponse> {
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const activities = await this.importService.getDividends({
dataSource,
symbol,
userCurrency
userId: this.request.user.id
});
return { activities };

View File

@ -4,11 +4,13 @@ import { OrderModule } from '@ghostfolio/api/app/order/order.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 { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.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 { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { Module } from '@nestjs/common';
@ -30,7 +32,9 @@ import { ImportService } from './import.service';
PortfolioModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule
SymbolProfileModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
],
providers: [ImportService]
})

View File

@ -9,16 +9,15 @@ import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { DATA_GATHERING_QUEUE_PRIORITY_HIGH } from '@ghostfolio/common/config';
import {
DATE_FORMAT,
getAssetProfileIdentifier,
parseDate
} from '@ghostfolio/common/helper';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import {
AccountWithPlatform,
OrderWithAccount,
@ -27,11 +26,13 @@ import {
import { Injectable } from '@nestjs/common';
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
import Big from 'big.js';
import { endOfToday, format, isAfter, isSameSecond, parseISO } from 'date-fns';
import { uniqBy } from 'lodash';
import { Big } from 'big.js';
import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns';
import { omit, uniqBy } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
import { ImportDataDto } from './import-data.dto';
@Injectable()
export class ImportService {
public constructor(
@ -39,7 +40,6 @@ export class ImportService {
private readonly configurationService: ConfigurationService,
private readonly dataGatheringService: DataGatheringService,
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly orderService: OrderService,
private readonly platformService: PlatformService,
private readonly portfolioService: PortfolioService,
@ -49,11 +49,16 @@ export class ImportService {
public async getDividends({
dataSource,
symbol,
userCurrency
}: UniqueAsset & { userCurrency: string }): Promise<Activity[]> {
userId
}: AssetProfileIdentifier & { userId: string }): Promise<Activity[]> {
try {
const { firstBuyDate, historicalData, orders } =
await this.portfolioService.getPosition(dataSource, undefined, symbol);
const { activities, firstBuyDate, historicalData } =
await this.portfolioService.getHolding({
dataSource,
symbol,
userId,
impersonationId: undefined
});
const [[assetProfile], dividends] = await Promise.all([
this.symbolProfileService.getSymbolProfiles([
@ -71,79 +76,84 @@ export class ImportService {
})
]);
const accounts = orders.map((order) => {
return order.Account;
});
const Account = this.isUniqueAccount(accounts) ? accounts[0] : undefined;
return Object.entries(dividends).map(([dateString, { marketPrice }]) => {
const quantity =
historicalData.find((historicalDataItem) => {
return historicalDataItem.date === dateString;
})?.quantity ?? 0;
const value = new Big(quantity).mul(marketPrice).toNumber();
const date = parseDate(dateString);
const isDuplicate = orders.some((activity) => {
return (
activity.accountId === Account?.id &&
activity.SymbolProfile.currency === assetProfile.currency &&
activity.SymbolProfile.dataSource === assetProfile.dataSource &&
isSameSecond(activity.date, date) &&
activity.quantity === quantity &&
activity.SymbolProfile.symbol === assetProfile.symbol &&
activity.type === 'DIVIDEND' &&
activity.unitPrice === marketPrice
);
const accounts = activities
.filter(({ account }) => {
return !!account;
})
.map(({ account }) => {
return account;
});
const error: ActivityError = isDuplicate
? { code: 'IS_DUPLICATE' }
: undefined;
const account = this.isUniqueAccount(accounts) ? accounts[0] : undefined;
return {
Account,
date,
error,
quantity,
value,
accountId: Account?.id,
accountUserId: undefined,
comment: undefined,
createdAt: undefined,
fee: 0,
feeInBaseCurrency: 0,
id: assetProfile.id,
isDraft: false,
SymbolProfile: <SymbolProfile>(<unknown>assetProfile),
symbolProfileId: assetProfile.id,
type: 'DIVIDEND',
unitPrice: marketPrice,
updatedAt: undefined,
userId: Account?.userId,
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
return await Promise.all(
Object.entries(dividends).map(async ([dateString, { marketPrice }]) => {
const quantity =
historicalData.find((historicalDataItem) => {
return historicalDataItem.date === dateString;
})?.quantity ?? 0;
const value = new Big(quantity).mul(marketPrice).toNumber();
const date = parseDate(dateString);
const isDuplicate = activities.some((activity) => {
return (
activity.accountId === account?.id &&
activity.SymbolProfile.currency === assetProfile.currency &&
activity.SymbolProfile.dataSource === assetProfile.dataSource &&
isSameSecond(activity.date, date) &&
activity.quantity === quantity &&
activity.SymbolProfile.symbol === assetProfile.symbol &&
activity.type === 'DIVIDEND' &&
activity.unitPrice === marketPrice
);
});
const error: ActivityError = isDuplicate
? { code: 'IS_DUPLICATE' }
: undefined;
return {
account,
date,
error,
quantity,
value,
assetProfile.currency,
userCurrency
)
};
});
accountId: account?.id,
accountUserId: undefined,
comment: undefined,
currency: undefined,
createdAt: undefined,
fee: 0,
feeInAssetProfileCurrency: 0,
feeInBaseCurrency: 0,
id: assetProfile.id,
isDraft: false,
SymbolProfile: assetProfile,
symbolProfileId: assetProfile.id,
type: 'DIVIDEND',
unitPrice: marketPrice,
unitPriceInAssetProfileCurrency: marketPrice,
updatedAt: undefined,
userId: account?.userId,
valueInBaseCurrency: value
};
})
);
} catch {
return [];
}
}
public async import({
accountsDto,
accountsWithBalancesDto,
activitiesDto,
isDryRun = false,
maxActivitiesToImport,
user
}: {
accountsDto: Partial<CreateAccountDto>[];
activitiesDto: Partial<CreateOrderDto>[];
accountsWithBalancesDto: ImportDataDto['accounts'];
activitiesDto: ImportDataDto['activities'];
isDryRun?: boolean;
maxActivitiesToImport: number;
user: UserWithSettings;
@ -151,12 +161,12 @@ export class ImportService {
const accountIdMapping: { [oldAccountId: string]: string } = {};
const userCurrency = user.Settings.settings.baseCurrency;
if (!isDryRun && accountsDto?.length) {
if (!isDryRun && accountsWithBalancesDto?.length) {
const [existingAccounts, existingPlatforms] = await Promise.all([
this.accountService.accounts({
where: {
id: {
in: accountsDto.map(({ id }) => {
in: accountsWithBalancesDto.map(({ id }) => {
return id;
})
}
@ -165,14 +175,19 @@ export class ImportService {
this.platformService.getPlatforms()
]);
for (const account of accountsDto) {
for (const accountWithBalances of accountsWithBalancesDto) {
// Check if there is any existing account with the same ID
const accountWithSameId = existingAccounts.find(
(existingAccount) => existingAccount.id === account.id
);
const accountWithSameId = existingAccounts.find((existingAccount) => {
return existingAccount.id === accountWithBalances.id;
});
// If there is no account or if the account belongs to a different user then create a new account
if (!accountWithSameId || accountWithSameId.userId !== user.id) {
const account: CreateAccountDto = omit(
accountWithBalances,
'balances'
);
let oldAccountId: string;
const platformId = account.platformId;
@ -185,7 +200,10 @@ export class ImportService {
let accountObject: Prisma.AccountCreateInput = {
...account,
User: { connect: { id: user.id } }
balances: {
create: accountWithBalances.balances ?? []
},
user: { connect: { id: user.id } }
};
if (
@ -195,7 +213,7 @@ export class ImportService {
) {
accountObject = {
...accountObject,
Platform: { connect: { id: platformId } }
platform: { connect: { id: platformId } }
};
}
@ -214,7 +232,7 @@ export class ImportService {
for (const activity of activitiesDto) {
if (!activity.dataSource) {
if (activity.type === 'ITEM' || activity.type === 'LIABILITY') {
if (['FEE', 'INTEREST', 'ITEM', 'LIABILITY'].includes(activity.type)) {
activity.dataSource = DataSource.MANUAL;
} else {
activity.dataSource =
@ -249,34 +267,31 @@ export class ImportService {
);
if (isDryRun) {
accountsDto.forEach(({ id, name }) => {
accountsWithBalancesDto.forEach(({ id, name }) => {
accounts.push({ id, name });
});
}
const activities: Activity[] = [];
for (let [
index,
{
accountId,
comment,
date,
error,
fee,
quantity,
SymbolProfile,
type,
unitPrice
}
] of activitiesExtendedWithErrors.entries()) {
for (const activity of activitiesExtendedWithErrors) {
const accountId = activity.accountId;
const comment = activity.comment;
const currency = activity.currency;
const date = activity.date;
const error = activity.error;
const fee = activity.fee;
const quantity = activity.quantity;
const SymbolProfile = activity.SymbolProfile;
const type = activity.type;
const unitPrice = activity.unitPrice;
const assetProfile = assetProfiles[
getAssetProfileIdentifier({
dataSource: SymbolProfile.dataSource,
symbol: SymbolProfile.symbol
})
] ?? {
currency: SymbolProfile.currency,
dataSource: SymbolProfile.dataSource,
symbol: SymbolProfile.symbol
};
@ -285,12 +300,14 @@ export class ImportService {
assetSubClass,
countries,
createdAt,
currency,
cusip,
dataSource,
figi,
figiComposite,
figiShareClass,
holdings,
id,
isActive,
isin,
name,
scraperConfiguration,
@ -310,43 +327,16 @@ export class ImportService {
Account?: { id: string; name: string };
});
if (SymbolProfile.currency !== assetProfile.currency) {
// Convert the unit price and fee to the asset currency if the imported
// activity is in a different currency
unitPrice = await this.exchangeRateDataService.toCurrencyAtDate(
unitPrice,
SymbolProfile.currency,
assetProfile.currency,
date
);
if (!unitPrice) {
throw new Error(
`activities.${index} historical exchange rate at ${format(
date,
DATE_FORMAT
)} is not available from "${SymbolProfile.currency}" to "${
assetProfile.currency
}"`
);
}
fee = await this.exchangeRateDataService.toCurrencyAtDate(
fee,
SymbolProfile.currency,
assetProfile.currency,
date
);
}
if (isDryRun) {
order = {
comment,
currency,
date,
fee,
quantity,
type,
unitPrice,
Account: validatedAccount,
accountId: validatedAccount?.id,
accountUserId: undefined,
createdAt: new Date(),
@ -357,12 +347,14 @@ export class ImportService {
assetSubClass,
countries,
createdAt,
currency,
cusip,
dataSource,
figi,
figiComposite,
figiShareClass,
holdings,
id,
isActive,
isin,
name,
scraperConfiguration,
@ -371,9 +363,10 @@ export class ImportService {
symbolMapping,
updatedAt,
url,
comment: assetProfile.comment
comment: assetProfile.comment,
currency: assetProfile.currency,
userId: dataSource === 'MANUAL' ? user.id : undefined
},
Account: validatedAccount,
symbolProfileId: undefined,
updatedAt: new Date(),
userId: user.id
@ -385,6 +378,7 @@ export class ImportService {
order = await this.orderService.createOrder({
comment,
currency,
date,
fee,
quantity,
@ -394,9 +388,10 @@ export class ImportService {
SymbolProfile: {
connectOrCreate: {
create: {
currency,
dataSource,
symbol
symbol,
currency: assetProfile.currency,
userId: dataSource === 'MANUAL' ? user.id : undefined
},
where: {
dataSource_symbol: {
@ -407,9 +402,14 @@ export class ImportService {
}
},
updateAccountBalance: false,
User: { connect: { id: user.id } },
user: { connect: { id: user.id } },
userId: user.id
});
if (order.SymbolProfile?.symbol) {
// Update symbol that may have been assigned in createOrder()
assetProfile.symbol = order.SymbolProfile.symbol;
}
}
const value = new Big(quantity).mul(unitPrice).toNumber();
@ -418,18 +418,8 @@ export class ImportService {
...order,
error,
value,
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
fee,
currency,
userCurrency
),
// @ts-ignore
SymbolProfile: assetProfile,
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
value,
currency,
userCurrency
)
SymbolProfile: assetProfile
});
}
@ -446,15 +436,16 @@ export class ImportService {
});
});
this.dataGatheringService.gatherSymbols(
uniqueActivities.map(({ date, SymbolProfile }) => {
this.dataGatheringService.gatherSymbols({
dataGatheringItems: uniqueActivities.map(({ date, SymbolProfile }) => {
return {
date,
dataSource: SymbolProfile.dataSource,
symbol: SymbolProfile.symbol
};
})
);
}),
priority: DATA_GATHERING_QUEUE_PRIORITY_HIGH
});
}
return activities;
@ -469,12 +460,13 @@ export class ImportService {
userCurrency: string;
userId: string;
}): Promise<Partial<Activity>[]> {
let { activities: existingActivities } = await this.orderService.getOrders({
userCurrency,
userId,
includeDrafts: true,
withExcludedAccounts: true
});
const { activities: existingActivities } =
await this.orderService.getOrders({
userCurrency,
userId,
includeDrafts: true,
withExcludedAccounts: true
});
return activitiesDto.map(
({
@ -493,7 +485,9 @@ export class ImportService {
const isDuplicate = existingActivities.some((activity) => {
return (
activity.accountId === accountId &&
activity.SymbolProfile.currency === currency &&
activity.comment === comment &&
(activity.currency === currency ||
activity.SymbolProfile.currency === currency) &&
activity.SymbolProfile.dataSource === dataSource &&
isSameSecond(activity.date, date) &&
activity.fee === fee &&
@ -511,6 +505,7 @@ export class ImportService {
return {
accountId,
comment,
currency,
date,
error,
fee,
@ -518,25 +513,19 @@ export class ImportService {
type,
unitPrice,
SymbolProfile: {
currency,
dataSource,
symbol,
assetClass: null,
assetSubClass: null,
comment: null,
countries: null,
activitiesCount: undefined,
assetClass: undefined,
assetSubClass: undefined,
countries: undefined,
createdAt: undefined,
figi: null,
figiComposite: null,
figiShareClass: null,
currency: undefined,
holdings: undefined,
id: undefined,
isin: null,
name: null,
scraperConfiguration: null,
sectors: null,
symbolMapping: null,
updatedAt: undefined,
url: null
isActive: true,
sectors: undefined,
updatedAt: undefined
}
};
}
@ -569,12 +558,13 @@ export class ImportService {
const assetProfiles: {
[assetProfileIdentifier: string]: Partial<SymbolProfile>;
} = {};
const dataSources = await this.dataProviderService.getDataSources({ user });
for (const [
index,
{ currency, dataSource, symbol, type }
] of activitiesDto.entries()) {
if (!this.configurationService.get('DATA_SOURCES').includes(dataSource)) {
if (!dataSources.includes(dataSource)) {
throw new Error(
`activities.${index}.dataSource ("${dataSource}") is not valid`
);
@ -611,12 +601,6 @@ export class ImportService {
`activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
);
}
if (assetProfile.currency !== currency) {
throw new Error(
`activities.${index}.currency ("${currency}") does not match with currency of ${assetProfile.symbol} ("${assetProfile.currency}")`
);
}
}
assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })] =

View File

@ -1,4 +1,4 @@
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor';
import { InfoItem } from '@ghostfolio/common/interfaces';
import { Controller, Get, UseInterceptors } from '@nestjs/common';

View File

@ -1,15 +1,15 @@
import { BenchmarkModule } from '@ghostfolio/api/app/benchmark/benchmark.module';
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';
import { BenchmarkModule } from '@ghostfolio/api/services/benchmark/benchmark.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.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 { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
import { DataGatheringModule } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
import { TagModule } from '@ghostfolio/api/services/tag/tag.module';
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
@ -32,8 +32,9 @@ import { InfoService } from './info.service';
PlatformModule,
PropertyModule,
RedisCacheModule,
SubscriptionModule,
SymbolProfileModule,
TagModule,
TransformDataSourceInResponseModule,
UserModule
],
providers: [InfoService]

View File

@ -1,19 +1,19 @@
import { BenchmarkService } from '@ghostfolio/api/app/benchmark/benchmark.service';
import { PlatformService } from '@ghostfolio/api/app/platform/platform.service';
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';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import {
DEFAULT_CURRENCY,
HEADER_KEY_TOKEN,
PROPERTY_BETTER_UPTIME_MONITOR_ID,
PROPERTY_COUNTRIES_OF_SUBSCRIBERS,
PROPERTY_DEMO_USER_ID,
PROPERTY_IS_READ_ONLY_MODE,
PROPERTY_SLACK_COMMUNITY_USERS,
PROPERTY_STRIPE_CONFIG,
ghostfolioFearAndGreedIndexDataSource
} from '@ghostfolio/common/config';
import {
@ -21,19 +21,13 @@ import {
encodeDataSource,
extractNumberFromString
} from '@ghostfolio/common/helper';
import {
InfoItem,
Statistics,
Subscription
} from '@ghostfolio/common/interfaces';
import { InfoItem, Statistics } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { SubscriptionOffer } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as cheerio from 'cheerio';
import { format, subDays } from 'date-fns';
import got from 'got';
@Injectable()
export class InfoService {
@ -47,16 +41,13 @@ export class InfoService {
private readonly platformService: PlatformService,
private readonly propertyService: PropertyService,
private readonly redisCacheService: RedisCacheService,
private readonly tagService: TagService,
private readonly subscriptionService: SubscriptionService,
private readonly userService: UserService
) {}
public async get(): Promise<InfoItem> {
const info: Partial<InfoItem> = {};
let isReadOnlyMode: boolean;
const platforms = await this.platformService.getPlatforms({
orderBy: { name: 'asc' }
});
const globalPermissions: string[] = [];
@ -73,9 +64,9 @@ export class InfoService {
}
if (this.configurationService.get('ENABLE_FEATURE_READ_ONLY_MODE')) {
isReadOnlyMode = (await this.propertyService.getByKey(
isReadOnlyMode = await this.propertyService.getByKey<boolean>(
PROPERTY_IS_READ_ONLY_MODE
)) as boolean;
);
}
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
@ -90,9 +81,9 @@ export class InfoService {
globalPermissions.push(permissions.enableSubscription);
info.countriesOfSubscribers =
((await this.propertyService.getByKey(
(await this.propertyService.getByKey<string[]>(
PROPERTY_COUNTRIES_OF_SUBSCRIBERS
)) as string[]) ?? [];
)) ?? [];
info.stripePublicKey = this.configurationService.get('STRIPE_PUBLIC_KEY');
}
@ -100,22 +91,28 @@ export class InfoService {
globalPermissions.push(permissions.enableSystemMessage);
}
const isUserSignupEnabled =
await this.propertyService.isUserSignupEnabled();
const [
benchmarks,
demoAuthToken,
isUserSignupEnabled,
platforms,
statistics,
subscriptionOffer
] = await Promise.all([
this.benchmarkService.getBenchmarkAssetProfiles(),
this.getDemoAuthToken(),
this.propertyService.isUserSignupEnabled(),
this.platformService.getPlatforms({
orderBy: { name: 'asc' }
}),
this.getStatistics(),
this.subscriptionService.getSubscriptionOffer({ key: 'default' })
]);
if (isUserSignupEnabled) {
globalPermissions.push(permissions.createUserAccount);
}
const [benchmarks, demoAuthToken, statistics, subscriptions, tags] =
await Promise.all([
this.benchmarkService.getBenchmarkAssetProfiles(),
this.getDemoAuthToken(),
this.getStatistics(),
this.getSubscriptions(),
this.tagService.get()
]);
return {
...info,
benchmarks,
@ -124,8 +121,7 @@ export class InfoService {
isReadOnlyMode,
platforms,
statistics,
subscriptions,
tags,
subscriptionOffer,
baseCurrency: DEFAULT_CURRENCY,
currencies: this.exchangeRateDataService.getCurrencies()
};
@ -137,12 +133,12 @@ export class InfoService {
AND: [
{
NOT: {
Analytics: null
analytics: null
}
},
{
Analytics: {
updatedAt: {
analytics: {
lastRequestAt: {
gt: subDays(new Date(), aDays)
}
}
@ -154,20 +150,15 @@ export class InfoService {
private async countDockerHubPulls(): Promise<number> {
try {
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
const { pull_count } = await got(
`https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio`,
const { pull_count } = (await fetch(
'https://hub.docker.com/v2/repositories/ghostfolio/ghostfolio',
{
headers: { 'User-Agent': 'request' },
// @ts-ignore
signal: abortController.signal
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json())) as { pull_count: number };
return pull_count;
} catch (error) {
@ -179,22 +170,17 @@ export class InfoService {
private async countGitHubContributors(): Promise<number> {
try {
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
const { body } = await got('https://github.com/ghostfolio/ghostfolio', {
// @ts-ignore
signal: abortController.signal
});
const body = await fetch('https://github.com/ghostfolio/ghostfolio', {
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}).then((res) => res.text());
const $ = cheerio.load(body);
return extractNumberFromString({
value: $(
`a[href="/ghostfolio/ghostfolio/graphs/contributors"] .Counter`
'a[href="/ghostfolio/ghostfolio/graphs/contributors"] .Counter'
).text()
});
} catch (error) {
@ -206,20 +192,15 @@ export class InfoService {
private async countGitHubStargazers(): Promise<number> {
try {
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
const { stargazers_count } = await got(
`https://api.github.com/repos/ghostfolio/ghostfolio`,
const { stargazers_count } = (await fetch(
'https://api.github.com/repos/ghostfolio/ghostfolio',
{
headers: { 'User-Agent': 'request' },
// @ts-ignore
signal: abortController.signal
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json())) as { stargazers_count: number };
return stargazers_count;
} catch (error) {
@ -235,7 +216,7 @@ export class InfoService {
AND: [
{
NOT: {
Analytics: null
analytics: null
}
},
{
@ -249,15 +230,15 @@ export class InfoService {
}
private async countSlackCommunityUsers() {
return (await this.propertyService.getByKey(
return await this.propertyService.getByKey<string>(
PROPERTY_SLACK_COMMUNITY_USERS
)) as string;
);
}
private async getDemoAuthToken() {
const demoUserId = (await this.propertyService.getByKey(
const demoUserId = await this.propertyService.getByKey<string>(
PROPERTY_DEMO_USER_ID
)) as string;
);
if (demoUserId) {
return this.jwtService.sign({
@ -314,47 +295,29 @@ export class InfoService {
return statistics;
}
private async getSubscriptions(): Promise<{
[offer in SubscriptionOffer]: Subscription;
}> {
if (!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
return undefined;
}
return (
((await this.propertyService.getByKey(PROPERTY_STRIPE_CONFIG)) as any) ??
{}
);
}
private async getUptime(): Promise<number> {
{
try {
const monitorId = (await this.propertyService.getByKey(
const monitorId = await this.propertyService.getByKey<string>(
PROPERTY_BETTER_UPTIME_MONITOR_ID
)) as string;
);
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
const { data } = await got(
const { data } = await fetch(
`https://uptime.betterstack.com/api/v2/monitors/${monitorId}/sla?from=${format(
subDays(new Date(), 90),
DATE_FORMAT
)}&to${format(new Date(), DATE_FORMAT)}`,
{
headers: {
Authorization: `Bearer ${this.configurationService.get(
[HEADER_KEY_TOKEN]: `Bearer ${this.configurationService.get(
'API_KEY_BETTER_UPTIME'
)}`
},
// @ts-ignore
signal: abortController.signal
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).json<any>();
).then((res) => res.json());
return data.attributes.availability / 100;
} catch (error) {

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