Compare commits

...

1070 Commits
2.66.1 ... main

Author SHA1 Message Date
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
Thomas Kaul
4c63e08e3c
Release 2.155.0 (#4595) 2025-04-23 20:29:27 +02:00
github-actions[bot]
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
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
10580e22d1
Feature/migrate value component to control flow (#4592)
* Migrate to control flow

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

* Update changelog
2025-04-23 20:15:31 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Kenrick Tandrian
416fa44cf0
Feature/create watchlist API endpoints (#4570)
* Create watchlist API endpoints

* Update changelog
2025-04-21 20:19:59 +02:00
Thomas Kaul
b77afed38c
Release 2.154.0 (#4583) 2025-04-21 19:49:15 +02:00
github-actions[bot]
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
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
github-actions[bot]
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
Tobias Kugel
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
Thomas Kaul
6b2966b7dc
Feature/extend benchmark detail dialog by market price (#4565)
* Add current market price

* Update changelog
2025-04-20 08:01:03 +02:00
Martin Clifford
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
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
f3022ca1f4
Release 2.153.0 (#4561) 2025-04-18 14:44:45 +02:00
Thomas Kaul
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
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
db38806812
Feature/refresh cryptocurrencies list 20250417 (#4548)
* Update cryptocurrencies.json

* Update changelog
2025-04-18 11:15:51 +02:00
Thomas Kaul
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
Thomas Kaul
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
csehatt741
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
github-actions[bot]
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
Thomas Kaul
073e60055d
Release 2.152.1 (#4553) 2025-04-17 20:35:12 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
535e9b654d
Release 2.152.0 (#4542) 2025-04-16 20:59:46 +02:00
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
github-actions[bot]
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
csehatt741
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
github-actions[bot]
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
Thomas Kaul
1856d40ad0
Release 2.151.0 (#4530) 2025-04-11 19:08:56 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
github-actions[bot]
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
csehatt741
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
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
3ec0fe81b4
Release 2.150.0 (#4511) 2025-04-05 11:15:43 +02:00
github-actions[bot]
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
Tobias Kugel
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
github-actions[bot]
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
Akd11111
b6fc5566d4
Feature/update messages.pl.xlf (#4499)
* Update messages.pl.xlf

* Update changelog
2025-04-04 22:11:08 +02:00
Marcin Szymański
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
Thomas Kaul
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
github-actions[bot]
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
lechtidu56
2894274b92
Feature/improve language localization for fr (#4502)
* Improve language localization for fr

* Update changelog
2025-04-04 19:37:19 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
1202d15522
Release 2.149.0 (#4495) 2025-03-30 10:35:31 +02:00
github-actions[bot]
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
Tobias Kugel
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
github-actions[bot]
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
Thomas Kaul
b720a8dd96
Feature/set up terms of service (#4490)
* Set up terms of service

* Update changelog
2025-03-30 09:59:56 +02:00
Thomas Kaul
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
Thomas Kaul
70d56c6c6c
Feature/add LinkedIn page to README.md (#4476)
* Add LinkedIn page
2025-03-27 14:23:12 +01:00
Thomas Kaul
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
Shaunak Das
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
Thomas Kaul
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
Thomas Kaul
f28c452be0
Release 2.148.0 (#4482) 2025-03-24 20:59:18 +01:00
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
44f6efd427
Feature/extend personal finance tools 20250323 (#4478)
* Add Asseta

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

* Add test cases
2025-03-22 17:52:16 +01:00
Thomas Kaul
a6952a0e37
Feature/extend get data sources in data provider service (#4473)
* Extend data sources
2025-03-22 17:51:43 +01:00
github-actions[bot]
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
csehatt741
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
Sayed Murtadha Ahmed
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
Omer Faruk Gormel
5ecdb5fb7a
Feature/improve language localization for tr 20250322 (#4467)
* Update translations

* Update changelog
2025-03-22 10:20:30 +01:00
Ronnie Alsop
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
csehatt741
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
Thomas Kaul
536b000ff9
Feature/improve Storybook story of fire calculator (#4451)
* Add default value for fire wealth
2025-03-21 20:41:37 +01:00
github-actions[bot]
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
Thomas Kaul
d8b305a343
Feature/add Storybook to sitemap.xml (#4452)
* Add Storybook

* Update changelog
2025-03-20 08:04:02 +01:00
Thomas Kaul
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
Szymon Łągiewka
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
github-actions[bot]
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
Thomas Kaul
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
csehatt741
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
Chang-Yen Tseng
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
Thomas Kaul
4db8c007f0
Bugfix/fix activities import with account balances (#4446)
* Fix import with account balances

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

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

* Update changelog
2025-03-16 10:42:35 +01:00
Thomas Kaul
29b8d63951
Release 2.146.0 (#4444) 2025-03-15 19:39:07 +01:00
Thomas Kaul
3efe06409e
Feature/improve language localization for de 20250315 (#4442)
* Update translations
2025-03-15 19:36:10 +01:00
Thomas Kaul
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
Thomas Kaul
755ab15755
Feature/format name in financial modeling prep service (#4441)
* Format name

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

* Update changelog
2025-03-15 18:25:15 +01:00
Thomas Kaul
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
Thomas Kaul
2f868b8902
Feature/add guard to TransformDataSourceInRequestInterceptor (#4438)
* Add guard
2025-03-15 12:22:24 +01:00
Ken Tandrian
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
Tobias Kugel
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
Thomas Kaul
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
github-actions[bot]
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
csehatt741
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
Tobias Kugel
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
Thomas Kaul
9abc4af203
Release 2.145.1 (#4422) 2025-03-10 20:47:27 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
7ca0720244
Release 2.145.0 (#4417) 2025-03-09 12:31:11 +01:00
Thomas Kaul
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
Thomas Kaul
52081d6741
Bugfix/exclude Storybook from html template middleware (#4414)
* Exclude storybook

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

* Update changelog
2025-03-09 10:56:39 +01:00
Thomas Kaul
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
Thomas Kaul
d346f6b9fa
Bugfix/fix changelog (#4412)
* Update changelog
2025-03-08 21:36:13 +01:00
Ken Tandrian
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
github-actions[bot]
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
Tobias Kugel
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
github-actions[bot]
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
csehatt741
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
Thomas Kaul
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
Thomas Kaul
25320e05d9
Release 2.144.0 (#4403) 2025-03-06 08:14:23 +01:00
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
ddb50295b2
Bugfix/fix changelog (#4400)
* Fix changelog
2025-03-05 20:54:32 +01:00
csehatt741
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
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
f3764f32bb
Release 2.143.0 (#4386) 2025-03-02 17:32:09 +01:00
Thomas Kaul
f63d171678
Feature/improve usability of no activities info (#4382)
* Improve usability

* Update changelog
2025-03-02 17:27:45 +01:00
Thomas Kaul
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
github-actions[bot]
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
Ken Tandrian
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
Thomas Kaul
51f0ecbfc1
Feature/add LinkedIn page to about page and footer (#4378)
* Add LinkedIn page

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

* Update changelog
2025-03-02 17:01:36 +01:00
Thomas Kaul
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
Matt Fiddaman
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
Thomas Kaul
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
Thomas Kaul
b3954b047f
Release 2.142.0 (#4373) 2025-02-28 21:02:20 +01:00
csehatt741
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
Thomas Kaul
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
Sayed Murtadha Ahmed
2f35f7225f
Feature/extend export by platforms (#4360)
* Extend export by platforms

* Update changelog
2025-02-27 20:59:12 +01:00
Shaunak Das
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
Thomas Kaul
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
Thomas Kaul
0349e17a8e
Feature/add security policy (#4363)
* Add security policy
2025-02-26 08:03:26 +01:00
Thomas Kaul
c25b45a2b8
Release 2.141.0 (#4361) 2025-02-25 19:51:14 +01:00
Thomas Kaul
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
csehatt741
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
Thomas Kaul
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
Shaunak Das
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
Sayed Murtadha Ahmed
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
Thomas Kaul
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
Thomas Kaul
c90dca565f
Release 2.140.0 (#4345) 2025-02-20 20:53:38 +01:00
github-actions[bot]
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
57957a7b30
Bugfix/add missing CommonModule in tags selector component (#4335)
* Add missing CommonModule (async pipe)
2025-02-17 21:03:01 +01:00
Shaunak Das
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
Ken Tandrian
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
Ken Tandrian
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
Thomas Kaul
57673046e7
Release 2.139.1 (#4326) 2025-02-15 15:52:25 +01:00
Thomas Kaul
e26b015407
Bugfix/cannot register cron jobs (#4325)
* Reorganize benchmark modules and move benchmarks endpoint
2025-02-15 15:50:41 +01:00
Thomas Kaul
239adc1045
Release 2.139.0 (#4320) 2025-02-15 10:55:37 +01:00
Thomas Kaul
ab9133fa24
Bugfix/fix gaps in chart of benchmark comparator (#4311)
* Fix benchmark interval

* Update changelog
2025-02-15 10:53:06 +01:00
github-actions[bot]
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
Thomas Kaul
f1ab3ff8e8
Feature/modernize tags endpoint (#4317)
* Modernize tags endpoint

* Update changelog
2025-02-15 10:00:14 +01:00
Guillermo Tomás Fernández Martín
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
Ken Tandrian
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
Thomas Kaul
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
Amandee Ellawala
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
Shaunak Das
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
Thomas Kaul
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
Shaunak Das
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
github-actions[bot]
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
Ken Tandrian
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
Ken Tandrian
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
Ken Tandrian
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
Thomas Kaul
a11e9c7f48
Release 2.138.0 (#4298) 2025-02-08 09:56:39 +01:00
Thomas Kaul
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
Shaunak Das
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
Thomas Kaul
5b786ba143
Feature/improve error handling in CoinGecko service (#4287)
* Improve error handling

* Update changelog
2025-02-07 10:32:25 +01:00
Ken Tandrian
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
Ken Tandrian
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
github-actions[bot]
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
Shaunak Das
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
Thomas Kaul
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
Thomas Kaul
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
github-actions[bot]
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
Thomas Kaul
e6416d5a00
Feature/improve mode value labels in scraper configuration (#4274)
* Improve mode value labels
2025-02-02 16:24:22 +01:00
Ken Tandrian
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
Thomas Kaul
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
Thomas Kaul
9ab21508a5
Feature/refactor snack bars (#4273)
* Refactor snack bars
2025-02-02 14:48:07 +01:00
Thomas Kaul
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
Thomas Kaul
f708d98710
Release 2.137.1 (#4268) 2025-02-01 16:55:19 +01:00
Thomas Kaul
f09946f72f
Release/2.137.0 (#4266)
* Release 2.137.0

* Update changelog
2025-02-01 15:13:02 +01:00
github-actions[bot]
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
Thomas Kaul
e2b27fb6e9
Feature/improve headers label in scraper configuration (#4263)
* Improve label
2025-02-01 13:04:28 +01:00
github-actions[bot]
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
Guillermo Tomás Fernández Martín
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
Thomas Kaul
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
Amandee Ellawala
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
github-actions[bot]
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
Shaunak Das
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
Ken Tandrian
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
Thomas Kaul
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
Ken Tandrian
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
6533f00bae
Release 2.136.0 (#4246) 2025-01-24 19:51:01 +01:00
Thomas Kaul
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
Guillermo Tomás Fernández Martín
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
Thomas Kaul
4b65b6277e
Feature/refresh cryptocurrencies list 20250123 (#4245)
* Update cryptocurrencies.json

* Update changelog
2025-01-23 21:43:09 +01:00
Thomas Kaul
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
Ken Tandrian
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
Miguel Borges de Freitas
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
Thomas Kaul
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
Ken Tandrian
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
Ken Tandrian
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
Serhii Serdiuk
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
Thomas Kaul
c72d219246
Release 2.135.0 (#4216) 2025-01-19 10:51:12 +01:00
Thomas Kaul
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
Thomas Kaul
663cee9a05
Feature/change wording in data providers of admin settings (#4207)
* Change wording
2025-01-19 10:39:05 +01:00
Thomas Kaul
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
Thomas Kaul
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
Ken Tandrian
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
Thomas Kaul
4e51a973c7
Feature/refactor is cryptocurrency check (#4215)
* Refactoring
2025-01-19 09:57:54 +01:00
Thomas Kaul
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
Thomas Kaul
4d201acdbe
Feature/extend get dividends in financial modeling prep service (#4210)
* Extend get dividends

* Update changelog
2025-01-19 09:39:05 +01:00
Ken Tandrian
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
Thomas Kaul
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
Thomas Kaul
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
Ken Tandrian
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
Thomas Kaul
996f7f3f40
Release 2.134.0 (#4197) 2025-01-15 20:36:14 +01:00
Ivan Kruglov
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
Thomas Kaul
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
Thomas Kaul
ec79a9efb6
Bugfix/fix exception in scraper configuration (#4196)
* Fix exception in scraper configuration

* Update changelog
2025-01-15 20:28:04 +01:00
Serhii Serdiuk
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
Thomas Kaul
5aad1b4434
Feature/improve language localization for de 20250112 (#4191)
* Update translations

* Update changelog
2025-01-13 17:31:53 +01:00
Thomas Kaul
d7171b9221
Feature/extend promotion system by label (#4181)
* Extend promotion system by label
2025-01-12 11:35:17 +01:00
Thomas Kaul
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
Szymon Łągiewka
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
Francisco Crizul
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
Thomas Kaul
ce85a14b11
Release 2.133.1 (#4186) 2025-01-09 18:30:21 +01:00
Thomas Kaul
86683a5bf3
Bugfix/fix AI prompt endpoint (#4185) 2025-01-09 18:28:42 +01:00
Thomas Kaul
a593caa94c
Release 2.133.0 (#4182) 2025-01-08 19:57:50 +01:00
Thomas Kaul
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
Karel De Smet
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
2e72ac7faf
Feature/format quotes in info service urls (#4174)
* Format quotes
2025-01-03 13:36:26 +01:00
Thomas Kaul
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
Thomas Kaul
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
Szymon Łągiewka
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
Thomas Kaul
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
Thomas Kaul
1cf7ffdee8
Feature/refresh cryptocurrencies list 20241230 (#4165)
* Update cryptocurrencies.json

* Update changelog
2025-01-01 19:20:27 +01:00
Thomas Kaul
b9917e72b2
Feature/update year to 2025 (#4169)
* Update year
2025-01-01 09:19:31 +01:00
Szymon Łągiewka
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
Lennart Goedhart
aca4c3d46d
Feature/modernize docker compose files (#4101)
* Modernize docker compose files

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

* Update changelog
2024-12-30 17:18:33 +01:00
Thomas Kaul
189808f9bf
Release 2.132.0 (#4160) 2024-12-30 11:28:35 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
8fc9d1c75c
Feature/clean up imports of holdings table component (#4159)
* Clean up imports
2024-12-30 11:23:26 +01:00
Thomas Kaul
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
Szymon Łągiewka
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
Thomas Kaul
ff7caf9c5c
Feature/migrate coupon redemption prompt dialog (#4150)
* Migrate coupon redemption prompt dialog

* Update changelog
2024-12-30 10:21:11 +01:00
Szymon Łągiewka
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
Karel De Smet
46cbbd3bc4
Feature/upgrade to nx 20.3 (#4152)
* Upgrade to Nx 20.3

* Update changelog
2024-12-29 19:47:32 +01:00
Thomas Kaul
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
Thomas Kaul
81f874bbc2
Feature/improve language localization for de 20241227 (#4148)
* Update translations

* Update changelog
2024-12-28 09:54:39 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
be4ac17a5c
Release 2.131.0 (#4143) 2024-12-25 17:08:06 +01:00
Thomas Kaul
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
Thomas Kaul
9a579dd884
Feature/change access tab icon on account page (#4137)
* Change icon

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

* Update changelog
2024-12-24 13:55:59 +01:00
Thomas Kaul
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
Thomas Kaul
c5fffed795
Release 2.130.0 (#4133) 2024-12-21 09:19:02 +01:00
Thomas Kaul
09c186f560
Feature/improve language localization for de 20241220 (#4131)
* Update translations

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

* Update changelog
2024-12-21 09:07:26 +01:00
Brandon
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
a776ea8864
Release 2.129.0 (#4124) 2024-12-14 11:40:17 +01:00
Thomas Kaul
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
Thomas Kaul
6ad9661d7f
Feature/improve language localization for de 20241214 (#4123)
* Update translations

* Update changelog
2024-12-14 11:19:15 +01:00
Thomas Kaul
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
Thomas Kaul
57659d2c04
Feature/add missing public keywords (#4102)
* Add public keyword
2024-12-13 17:13:51 +01:00
Thomas Kaul
0bd1a94a7b
Release 2.128.0 (#4120) 2024-12-12 19:18:38 +01:00
Amandee Ellawala
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
Thomas Kaul
f3712b293c
Bugfix/fix exception in portfolio calculator (#4119)
* Add guard

* Update changelog
2024-12-12 18:29:28 +01:00
Thomas Kaul
6341a6de6a
Feature/refactor X-ray summary (#4116)
* Refactor report statistics
2024-12-12 15:23:13 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
618a918423
Release 2.127.0 (#4114) 2024-12-08 18:59:33 +01:00
Thomas Kaul
0841f8bd5b
Bugfix/fix exception in portfolio calculator (#4113)
* Fix exception in portfolio calculator

* Update changelog
2024-12-08 18:57:22 +01:00
Thomas Kaul
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
Thomas Kaul
d6357487ea
Release 2.126.1 (#4110) 2024-12-07 21:43:30 +01:00
Thomas Kaul
ebef361d63
Bugfix/fix exception in portfolio calculator (#4109)
* Fix exception in portfolio calculator

* Update changelog
2024-12-07 21:41:21 +01:00
Thomas Kaul
17ffb29275
Release 2.126.0 (#4105) 2024-12-07 15:54:29 +01:00
Thomas Kaul
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
Thomas Kaul
0e01674552
Feature/set hashedKey of ApiKey to unique (#4103)
* Set hashedKey to unique
2024-12-07 15:44:53 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Amandee Ellawala
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
Ayush2198-source
c85a1be3cf
Feature/add pagination to users table (#4092)
* Add pagination to users table

* Update changelog
2024-12-03 20:13:45 +01:00
Thomas Kaul
dbfac54af9
Feature/refactor community language method (#4071)
* Refactoring
2024-12-02 17:32:24 +01:00
Thomas Kaul
66cace0c56
Release 2.125.0 (#4089) 2024-11-30 19:17:10 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
3194ed2145
Feature/improve usability of Ghostfolio data provider (#4086)
* Improve usability
2024-11-30 17:30:49 +01:00
Thomas Kaul
11a32a75c6
Feature/upgrade prisma to version 6 (#4082)
* Upgrade prisma to version 6

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

* Update changelog
2024-11-29 21:08:19 +01:00
Thomas Kaul
66bbbc2cb8
Feature/improve error handling in Ghostfolio data provider (#4079)
* Improve error handling
2024-11-29 20:21:46 +01:00
Thomas Kaul
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
Thomas Kaul
8fe767a4e3
Feature/increase default request timeout (#4075)
* Increase default request timeout

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

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

* Update changelog
2024-11-27 08:10:20 +01:00
Thomas Kaul
e4e05fac94
Release 2.124.1 (#4072) 2024-11-25 21:28:36 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
18de790f36
Release 2.124.0 (#4066) 2024-11-24 19:52:07 +01:00
Thomas Kaul
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
Thomas Kaul
0bc52fd80e
Feature/harmonize log messages in data provider services (#4064)
* Harmonize log messages
2024-11-24 08:59:09 +01:00
Thomas Kaul
a8ea8a4599
Feature/refactor types in EOD historical data service (#4063)
* Refactor types
2024-11-23 20:35:51 +01:00
Jory Hogeveen
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
QURBAN AHMAD
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
Thomas Kaul
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
Thomas Kaul
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
QURBAN AHMAD
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
Thomas Kaul
4b0e75b26c
Release/2.123.0 (#4052)
* Release 2.123.0

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

* Update changelog
2024-11-16 18:44:04 +01:00
Thomas Kaul
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
Thomas Kaul
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
Amandee Ellawala
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
Thomas Kaul
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
Thomas Kaul
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
Mohan
92b025bff3
Feature/separate FIRE and X-ray pages (#4037)
* Separate FIRE / X-ray page

* Update changelog
2024-11-11 21:07:02 +01:00
Thomas Kaul
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
Thomas Kaul
09a9148fec
Bugfix/move changelog entry (#4038)
* Move changelog entry
2024-11-10 11:01:39 +01:00
Amandee Ellawala
6057794eb6
Feature/extend assistant by holding selector (#4031)
* Extend assistant by holding selector

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

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

* Update changelog
2024-11-07 20:05:01 +01:00
Thomas Kaul
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
Thomas Kaul
190bb4b6fb
Feature/refactor data gathering items (#4032)
* Refactoring
2024-11-07 19:57:37 +01:00
Thomas Kaul
04d5416c6d
Feature/harmonize date formats in import test files (#3910)
* Harmonize date formats
2024-11-06 18:09:44 +01:00
Thomas Kaul
9c27fb70aa
Feature/minor improvements in data provider service (#4017)
* Refactoring
2024-11-05 13:01:53 +01:00
Thomas Kaul
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
Thomas Kaul
93001b68ad
Feature/introduce lookup response interface (#4019)
* Introduce lookup response interface

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

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

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

* Update changelog
2024-11-02 13:36:33 +01:00
Thomas Kaul
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
dw-0
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
Thomas Kaul
d7f69020c2
Release 2.120.0 (#4004) 2024-10-30 21:41:47 +01:00
Thomas Kaul
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
Thomas Kaul
10e725b51a
Feature/Add dataProviderGhostfolioDailyRequests to Analytics (#4001)
* Add dataProviderGhostfolioDailyRequests to Analytics
2024-10-30 21:39:20 +01:00
Thomas Kaul
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
dw-0
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
Thomas Kaul
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
Thomas Kaul
6a195a7a36
Feature/extend sitemap.xml by resources sub pages in de (#3993)
* Extend sitemap.xml
2024-10-28 20:03:10 +01:00
ksyasuda
e61d797d2d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-27 07:44:30 -07:00
Thomas Kaul
0f19cb37e6
Feature/improve language localization for de 20241026 (#3991)
* Update translations

* Update changelog
2024-10-27 08:57:29 +01:00
ksyasuda
c9c066c8f7 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-26 15:43:30 -07:00
Sony Thomas
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
ksyasuda
8e2e73137c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-26 07:42:30 -07:00
dw-0
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
dw-0
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
dw-0
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
Thomas Kaul
906e526cb9
Release 2.119.0 (#3990) 2024-10-26 10:31:24 +02:00
Thomas Kaul
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
Thomas Kaul
4104fb2f8f
Feature/Improve subscription service (#3989)
* Various improvements
2024-10-26 10:29:04 +02:00
dw-0
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
Thomas Kaul
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
dw-0
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
ksyasuda
6bc4ffe03d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-25 11:41:30 -07:00
dw-0
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
ksyasuda
0c2063343d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-24 19:40:31 -07:00
Amandee Manushika
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
ksyasuda
3984d4363b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-23 19:39:30 -07:00
ksyasuda
52164b4994 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-23 11:38:30 -07:00
Thomas Kaul
a0445740c4
Release 2.118.0 (#3976) 2024-10-23 20:38:29 +02:00
dw-0
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
Ivan Skvortsov
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
Veer Chaurasia
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
ksyasuda
0718a2e3f9 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-22 19:37:30 -07:00
ksyasuda
75b8f5828d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-22 11:37:30 -07:00
vitalymatyushik
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
asiananimal
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
Thomas Kaul
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
ksyasuda
bdf9461148 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-21 19:36:01 -07:00
dw-0
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
ksyasuda
277a1cb38d Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-20 11:33:30 -07:00
Thomas Kaul
a5331dd32b
Feature/optimize dialog sizes for mobile (#3964)
* Optimize dialog sizes for mobile

* Update changelog
2024-10-20 17:59:44 +02:00
dw-0
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
ksyasuda
6d67d037eb Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-19 19:32:30 -07:00
Thomas Kaul
6abb94ba33
Feature/improve language localization for de 20241019 (#3960)
* Update translations

* Update changelog
2024-10-19 21:04:07 +02:00
Thomas Kaul
6ecd7ac768
Release 2.117.0 (#3962) 2024-10-19 21:02:47 +02:00
Wasif Mujahid
524adfc672
Feature/Clean up unused OnInit implementations (#3961)
* Clean up unused OnInit implementations
2024-10-19 20:59:42 +02:00
ksyasuda
788c9d853c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-19 11:32:01 -07:00
Thomas Kaul
308d3b64b1
Feature/Remove empty constructors (#3958)
* Remove empty constructors
2024-10-19 18:54:49 +02:00
Thomas Kaul
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
Thomas Kaul
68cb4b27d1
Feature/add logotype to footer (#3947)
* Add logo type to footer

* Update changelog
2024-10-19 15:20:15 +02:00
vitalymatyushik
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
Thomas Kaul
6bb7c0d196
Bugfix/fix style selector in carousel component (#3948)
* Fix style selector

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

* Add FIREkit
2024-10-17 20:42:24 +02:00
ksyasuda
2903cb396e Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-17 11:29:30 -07:00
Thomas Kaul
cfd5b6bbf2
Release 2.116.0 (#3942) 2024-10-17 17:46:03 +02:00
Thomas Kaul
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
Thomas Kaul
7f30424792
Feature/improve empty state in benchmarks of markets overview (#3939)
* Improve empty state

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

* Update changelog
2024-10-16 21:19:38 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
JN Brisset
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
Thomas Kaul
144e5da753
Feature/add units to x ray rule settings (#3926)
* Introduce unit

* Update changelog
2024-10-15 20:49:04 +02:00
vitalymatyushik
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
Thomas Kaul
ab10b9da54
Feature/improve language localization for de 20241014 (#3927)
* Update translations

* Update changelog
2024-10-15 20:01:19 +02:00
Thomas Kaul
6be8565442
Release 2.115.0 (#3925) 2024-10-14 21:41:50 +02:00
vitalymatyushik
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
dw-0
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
Madhab Chandra Sahoo
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
Thomas Kaul
f8da265f5f
Feature/restructure XRayRulesSettings (#3898)
* Restructure XRayRulesSettings

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

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

* Update changelog
2024-10-12 19:32:34 +02:00
ksyasuda
4db5e68f5c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-12 07:32:53 -07:00
ceroma
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
ksyasuda
f9c6a0cef3 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-11 15:32:44 -07:00
Dhaneshwari Tendle
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
Uday R
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
Madhab Chandra Sahoo
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
ksyasuda
69ab792b46 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-10 15:32:31 -07:00
Thomas Kaul
f5c0d803a0
Release 2.114.0 (#3905) 2024-10-10 21:09:58 +02:00
Uday R
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
Thomas Kaul
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
ksyasuda
f3927b25a4 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-09 15:32:24 -07:00
Matej Gerek
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
ksyasuda
8db7996539 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-08 15:32:09 -07:00
dw-0
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
ksyasuda
d93fec71b1 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-07 15:32:00 -07:00
Thomas Kaul
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
ksyasuda
2725b94169 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-06 23:31:49 -07:00
Thomas Kaul
14b88edff6
Feature/extend MSFT buy with dividend test (#3891)
* Extend test
2024-10-06 21:07:07 +02:00
Thomas Kaul
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
Thomas Kaul
adf9a71cbd
Release 2.113.0 (#3886) 2024-10-06 17:08:10 +02:00
Thomas Kaul
bce18f7261
Feature/reuse advanced markets calculation in portfolio details endpoint (#3884) 2024-10-06 17:05:06 +02:00
dw-0
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
8cbefbc1f4
Feature/move prisma to devDependencies (#3880) 2024-10-05 21:11:51 +02:00
dw-0
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
ksyasuda
dc3f25583a Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-05 11:31:35 -07:00
ceroma
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
Thomas Kaul
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
dw-0
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
ksyasuda
9a2ca9cd08 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-05 03:31:33 -07:00
dw-0
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
Thomas Kaul
f3e2091ff4
Feature/refactor markets calculation in details of portfolio service (#3871)
* Refactor markets calculation
2024-10-05 09:58:09 +02:00
ksyasuda
a597a58dad Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-04 19:31:30 -07:00
Thomas Kaul
bcc8cb1e5b
Feature/modernize rules implementation (#3869)
* Modernize rules implementation

* Include markets in details
2024-10-04 21:44:07 +02:00
dandevaud
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
ksyasuda
6fa0400980 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-03 19:31:19 -07:00
Thomas Kaul
c4742d3a53
Release 2.112.0 (#3864) 2024-10-03 20:43:04 +02:00
dw-0
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
Thomas Kaul
9b8f552ee2
Feature/improve language localization for de 20241003 (#3863)
* Update translations
2024-10-03 20:35:34 +02:00
Thomas Kaul
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
dw-0
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
ksyasuda
3e418b10fd Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-02 19:31:09 -07:00
Dmytro Werner
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
ksyasuda
84457f4816 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-02 11:31:06 -07:00
Prakhar Sharma
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
ceroma
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
ksyasuda
a36404d375 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-10-01 19:31:03 -07:00
Shaunak Das
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
Thomas Kaul
1b2a7dc2e4
Feature/improve language localization for de 20240929 (#3844)
* Update translations

* Update changelog
2024-10-01 18:39:42 +02:00
ksyasuda
a6d68ae296 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-30 19:30:53 -07:00
Thomas Kaul
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
ksyasuda
5deeb8861e Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-29 16:25:20 -07:00
Thomas Kaul
d00d7ac1dd
Feature/add support for archived product page (#3843)
* Add support for archived product page
2024-09-29 19:27:02 +02:00
Thomas Kaul
04e0bbd4c1
Feature/add tooltips to product page (#3842)
* Add tooltips
2024-09-29 19:26:30 +02:00
ksyasuda
0b596b6541 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-28 16:25:12 -07:00
ksyasuda
a05203c785 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-28 12:25:12 -07:00
Thomas Kaul
00f06d6aef
Release 2.111.0 (#3838) 2024-09-28 20:44:21 +02:00
Thomas Kaul
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
Thomas Kaul
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
ceroma
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
Thomas Kaul
ed30549f6b
Feature/prepare permission to delete asset profile of currency (#3833) 2024-09-28 09:47:48 +02:00
Thomas Kaul
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
Madhab Chandra Sahoo
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
Thomas Kaul
64c1d25e64
Feature/improve language localization for de 20240926 (#3823)
* Update translations

* Update changelog
2024-09-27 20:15:47 +02:00
Thomas Kaul
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
Thomas Kaul
ba438c25ef
Release 2.110.0 (#3816) 2024-09-24 19:47:46 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
ksyasuda
1ed71ed7ec Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-24 00:15:23 -07:00
Thomas Kaul
46fb075ecb
Feature/improve language localization for de 20240921 (#3804)
* Update translations

* Update changelog
2024-09-23 19:49:00 +02:00
Thomas Kaul
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
Thomas Kaul
67aa76d31c
Release 2.109.0 (#3803) 2024-09-21 20:03:31 +02:00
Thomas Kaul
f59e8c8798
Feature/add feature graphic for hacktoberfest 2024 blog post (#3800)
* Add feature graphic
2024-09-21 20:01:33 +02:00
Thomas Kaul
336f7b002c
Feature/extend personal finance tools (#3801)
* Add Finanzfluss Copilot
2024-09-21 20:00:56 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
5e4201d831
Feature/remove nx cloud access token (#3798)
* Remove nxCloudAccessToken
2024-09-21 15:04:52 +02:00
Thomas Kaul
20d709380a
Feature/add no auto-renewal hint to account membership page (#3795)
* Add hint
2024-09-21 14:34:17 +02:00
Thomas Kaul
7053aba2da
Feature/add portfolio performance metrics to public page (#3793)
* Add portfolio performance metrics

* Update changelog
2024-09-21 13:28:49 +02:00
Thomas Kaul
c2f69501c7
Feature/improve documentation for public API (#3792) 2024-09-21 11:34:26 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
7761e4d712
Feature/add hacktoberfest 2024 blog post (#3790)
* Add blog post: Hacktoberfest 2024

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

* Update changelog
2024-09-20 11:10:19 +02:00
Thomas Kaul
2bd14b135c
Feature/extract locales 20240919 (#3785) 2024-09-19 21:30:49 +02:00
Thomas Kaul
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
Thomas Kaul
cb472c0884
Feature/improve ghostfolio po polsku (#3782)
* Improve Ghostfolio po polsku
2024-09-19 20:28:09 +02:00
karolsol
d1f6601c5e
Feature/Improve language localization for pl (#3780)
* Update translations

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

* Update changelog
2024-09-18 15:58:19 +02:00
Thomas Kaul
22127b5915
Release 2.108.0 (#3777) 2024-09-17 20:17:23 +02:00
Thomas Kaul
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
Shaunak Das
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
Thomas Kaul
df9a0ec35a
Feature/support bonds in import dividend dialog (#3775)
* Support bonds

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

* Update changelog
2024-09-16 15:06:02 +02:00
Nikolai
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
ksyasuda
1b90fba656 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-14 14:58:43 -07:00
Thomas Kaul
fbf377f67f
Feature/set up rule settings dialog (#3771) 2024-09-14 20:44:36 +02:00
Thomas Kaul
3de192c65e
Feature/expose thresholds in rule settings (#3770) 2024-09-14 19:42:37 +02:00
Thomas Kaul
9fb80e5067
Feature/remove accounts from holding endpoint (#3765)
* Clean up accounts
2024-09-14 17:29:52 +02:00
Thomas Kaul
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
Shaunak Das
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
Thomas Kaul
8735fc3fad
Bugfix/fix typo in changelog (#3758) 2024-09-14 10:27:12 +02:00
ksyasuda
3eba400d21 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-09-12 19:05:35 -07:00
Thomas Kaul
7b8dc480f4
Release 2.107.1 (#3757) 2024-09-12 20:35:42 +02:00
Thomas Kaul
5a4f1c03cb
Feature/extend personal finance tools (#3751)
Add etops
2024-09-12 20:34:06 +02:00
Thomas Kaul
0ef2b82852
Bugfix/fix destructuring in activities filters (#3756)
* Provide default value during destructuring

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

* Update changelog
2024-09-10 20:32:08 +02:00
Shaunak Das
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
Thomas Kaul
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
FrozenPasta
9edffd100e
Feature/improve language localization for fr (#3724)
* Update translations

* Update changelog
2024-09-10 17:36:24 +02:00
Thomas Kaul
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
Thomas Kaul
557a0bf808
Feature/optimize info endpoint using promise.all (#3742)
* Optimize by using Promise.all()

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

* Update changelog
2024-09-08 21:41:41 +02:00
Thomas Kaul
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
Thomas Kaul
728f84e7eb
Release 2.106.0 (#3738) 2024-09-07 21:24:33 +02:00
Shaunak Das
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
fb44933c9c
Feature/improve error logs in scraper configuration test (#3730)
* Improve error logs

* Update changelog
2024-09-05 18:21:32 +02:00
Shaunak Das
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
Daniel Idem
8018236942
Bugfix/fix carousel component (#3709)
* Fix carousel component

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

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

* Update changelog
2024-08-31 16:11:52 +02:00
Thomas Kaul
c6f804f68c
Feature/Improve redis cache part 2 (#3716)
* Set ttl to 0
2024-08-31 16:11:18 +02:00
Thomas Kaul
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
Thomas Kaul
0fc83486dc
Feature/improve redis cache (#3714)
* Improve redis cache

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

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

* Update changelog
2024-08-28 20:28:11 +02:00
ksyasuda
da38abff62 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-27 17:46:04 -07:00
Thomas Kaul
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
ksyasuda
7d80d1ad3c Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-26 13:45:49 -07:00
Thomas Kaul
80330782d2
Release 2.106.0-beta.2 (#3702) 2024-08-26 16:23:34 +02:00
Thomas Kaul
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
ksyasuda
633995c9c8 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-25 12:51:55 -07:00
Thomas Kaul
4a8142b326
Release 2.106.0-beta.1 (#3699) 2024-08-25 10:15:01 +02:00
Thomas Kaul
7db7eeecf2
Bugfix/fix view mode toggle of holdings tab (#3698)
* Fix view mode toggle

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

* Update changelog
2024-08-25 09:02:14 +02:00
ksyasuda
b334ab351b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-24 15:36:16 -07:00
Thomas Kaul
c28af12cbd
Release 2.106.0-alpha.1 (#3694) 2024-08-24 17:03:20 +02:00
gizmodus
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
ksyasuda
4984d79bc7 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-23 15:36:11 -07:00
Thomas Kaul
84d23764db
Feature/minor improvements in client (#3690)
* Improve view mode form control value change

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

* Update changelog
2024-08-21 19:43:27 +02:00
Bastien Jeannelle
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
Thomas Kaul
ac5aec9262
Feature/extend personal finance tools 20240820 (#3685)
* Add Ziggma
2024-08-21 19:36:45 +02:00
ksyasuda
32f3c3a1f2 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-20 07:35:42 -07:00
Thomas Kaul
f883469b55
Feature/move development instructions to DEVELOPMENT.md (#3680)
* Move development instructions
2024-08-20 11:43:17 +02:00
Thomas Kaul
9f5707d4f5
Feature/extend personal finance tools 20240819 (#3682)
* Add CoinTracking
2024-08-20 11:42:44 +02:00
ksyasuda
dc3a21936b Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-18 07:35:20 -07:00
Anatoly Popov
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
ksyasuda
6d931134dd Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-17 23:35:17 -07:00
ksyasuda
2de959f080 Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-17 19:35:17 -07:00
Thomas Kaul
6c79ccb2a9
Release 2.104.1 (#3679) 2024-08-17 22:34:56 +02:00
Thomas Kaul
bce9b2f4bb
Bugfix/fix clone functionality of activity (#3678)
* Fix clone functionality

* Update changelog
2024-08-17 22:32:23 +02:00
Thomas Kaul
25ea451eb6 Release 2.104.0 (#3676) 2024-08-17 13:01:23 -07:00
Daniel Idem
d99b1a4cec Feature/reuse notification service for confirm dialogs (#3671)
* Reuse notification service for confirm dialogs
2024-08-17 13:01:23 -07:00
Thomas Kaul
bb892449ca Bugfix/fix parse date in date helper (#3675)
Handle empty date string
2024-08-17 13:01:23 -07:00
Thomas Kaul
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
Thomas Kaul
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
Daniel Idem
581651ef1d Feature/reuse notification service for alert dialogs (#3670)
* Reuse notification service for alert dialogs
2024-08-17 13:01:23 -07:00
Thomas Kaul
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
Thomas Kaul
7b2d8e4d3a
Release 2.104.0 (#3676) 2024-08-17 17:05:42 +02:00
Daniel Idem
952c2b71a2
Feature/reuse notification service for confirm dialogs (#3671)
* Reuse notification service for confirm dialogs
2024-08-17 16:55:49 +02:00
Thomas Kaul
26277803c6
Bugfix/fix parse date in date helper (#3675)
Handle empty date string
2024-08-17 16:21:13 +02:00
Thomas Kaul
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
Thomas Kaul
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
Daniel Idem
56ddbaf972
Feature/reuse notification service for alert dialogs (#3670)
* Reuse notification service for alert dialogs
2024-08-15 11:36:55 +02:00
Thomas Kaul
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
ksyasuda
efed1d3e1a
Merge branch 'main' of gitea.suda.codes:giteauser/ghostfolio-mirror 2024-08-13 22:27:04 -07:00
Thomas Kaul
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
Thomas Kaul
706784f7a0 Feature/set up notification service (#3663)
* Set up notification service

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

* Update changelog
2024-08-13 00:22:35 -07:00
Nuno
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
Thomas Kaul
001091a413 Feature/improve language localization for de 20240810 (#3660)
* Update translations

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

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

* Update changelog
2024-08-11 15:43:02 +02:00
Nuno
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
Thomas Kaul
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
Thomas Kaul
46553da2c3
Release 2.103.0 (#3658) 2024-08-10 09:02:49 +02:00
Thomas Kaul
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
Thomas Kaul
2bbad8f4b0
Feature/add logging to benchmark service (#3654)
* Add logging
2024-08-10 07:58:16 +02:00
Thomas Kaul
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
Thomas Kaul
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
Tiago Sousa
9f5da976f2
Feature/improve language localization for portuguese (#3651)
* Update translations

* Update changelog
2024-08-09 19:38:03 +02:00
Thomas Kaul
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
ksyasuda
1fd0c08477
update files 2024-08-09 02:19:48 -07:00
ksyasuda
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
ksyasuda
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
ksyasuda
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
Nuno
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
Thomas Kaul
43f5bb7773
Release 2.102.0 (#3648) 2024-08-07 20:46:57 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
4410040a14
Feature/update angular url in README.md (#3566)
Update Angular url
2024-08-06 16:53:31 +02:00
Thomas Kaul
b2ed0b2c80
Feature/improve caching of benchmarks in markets overview (#3640)
* Improve caching

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

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

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

* Update changelog
2024-08-04 08:56:55 +02:00
Thomas Kaul
757ff527d0
Feature/extend personal finance tools 20240803 (#3634)
* Add Capitalyse
2024-08-04 08:35:01 +02:00
Thomas Kaul
41f5801b5e
Feature/refactor unique asset type to asset profile identifier (#3636)
* Refactoring
2024-08-04 08:27:05 +02:00
Thomas Kaul
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
Thomas Kaul
aef650753e
Feature/clean up activities page (#3635)
* Clean up
2024-08-04 08:18:54 +02:00
Thomas Kaul
420f331be9
Release 2.101.0 (#3632) 2024-08-03 16:58:08 +02:00
Nuno
e0068c4d5d
Feature/harden container security following OWASP best practices (#3614)
* Harden container security

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

* Update changelog
2024-08-03 15:46:01 +02:00
Thomas Kaul
2fa723dc3c
Bugfix/fix language selector of user account settings (#3613)
Fix value of Català
2024-08-03 15:42:21 +02:00
Thomas Kaul
a500fb72c5
Feature/refactor Angular Material theme (#3629) 2024-08-02 20:57:03 +02:00
Thomas Kaul
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
David Requeno
c87b08ca8b
Feature/improve language localization for es (#3625)
* Update translations

* Update changelog
2024-08-01 20:28:42 +02:00
Thomas Kaul
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
BernatVM
7efda2f890
Feature/improve language localization for Catalan (#3598)
* Update translations

* Update changelog
2024-07-30 14:30:08 +02:00
Thomas Kaul
3794a61d2d
Release 2.99.0 (#3618) 2024-07-29 20:10:26 +02:00
Vijayaraj
c1d1ea9dde
Feature/migrate from Yarn 1 (Classic) to npm (#3601)
* Migrate from yarn to npm
2024-07-29 20:08:43 +02:00
Thomas Kaul
0d676a46c8
Release 2.98.0 (#3615) 2024-07-27 19:53:44 +02:00
Thomas Kaul
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
Thomas Kaul
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
Gerard Du Pre
f3f359bcfb
Feature/Improve language localization for spanish (#3612)
* Update messages.es.xlf
2024-07-26 15:55:26 +02:00
Thomas Kaul
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
Thomas Kaul
e228b4925c
Feature/update notes of personal finance tools (#3611)
* Update notes
2024-07-25 19:38:52 +02:00
Thomas Kaul
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
David Requeno
6af885fde0
Feature/improve language localization for Spanish (#3605)
* Improve language localization for Spanish

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

* Update changelog
2024-07-23 21:00:20 +02:00
Thomas Kaul
43fca7ff43
Feature/improve personal finance tools product page (#3599)
* Localize origin
* Localize regions
* Localize tags
2024-07-23 20:59:23 +02:00
Thomas Kaul
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
Thomas Kaul
d2ea7a0bfb
Feature/upgrade nx to version 19.5.1 (#3596)
* Upgrade angular and Nx

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

* Update changelog
2024-07-20 17:13:44 +02:00
Thomas Kaul
5601299648
Release 2.97.0 (#3592) 2024-07-20 11:24:47 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
f4a8acdb46
Feature/add selfh.st logo to landing page (#3582)
* Add selfh.st

* Update changelog
2024-07-20 10:13:28 +02:00
Thomas Kaul
1d6ba22598
Feature/improve language localization for de (#3583)
* Update translations
2024-07-20 10:12:49 +02:00
Thomas Kaul
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
Thomas Kaul
da5be3fb57
Feature/reuse open-color in portfolio proportion chart component (#3562)
* Reuse open-color
2024-07-18 10:14:12 +02:00
Thomas Kaul
b5317a7f95
Feature/improve language localization for de 20240715 (#3574)
* Update translations

* Update changelog
2024-07-17 17:37:56 +02:00
Thomas Kaul
43afb16808
Feature/introduce isUsedByUsersWithSubscription flag (#3573) 2024-07-16 20:51:49 +02:00
Thomas Kaul
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
Thomas Kaul
b94c1f280b
Bugfix/fix spacing on pricing page (#3571)
* Fix spacing
2024-07-16 20:42:41 +02:00
Thomas Kaul
acc59866a3
Bugfix/fix table sorting of holdings (#3572)
* Hide holdings table to fix sorting

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

* Update changelog
2024-07-13 20:11:40 +02:00
Thomas Kaul
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
Thomas Kaul
9d6214e93a
Bugfix/fix fees calculation in portfolio summary (#3567)
* Fix fees calculation

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

* Update changelog
2024-07-13 11:40:45 +02:00
Thomas Kaul
6eb9d9d973
Feature/extend personal finance tools 20240713 (#3565) 2024-07-13 11:40:29 +02:00
Thomas Kaul
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
Thomas Kaul
96434c5a54
Release 2.95.0 (#3561) 2024-07-12 21:04:38 +02:00
Thomas Kaul
4063c62a17
Feature/setup treemap chart for holdings (#3560)
* Setup treemap chart

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

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

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

* Update changelog
2024-07-07 09:54:54 +02:00
Thomas Kaul
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
Thomas Kaul
d49b90d7a5
Feature/refresh cryptocurrencies list 20240706 (#3544)
* Refresh cryptocurrencies list

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

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

* Update changelog
2024-07-07 08:25:51 +02:00
Thomas Kaul
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
Achal
7067aca04b
Feature/replace twitter.com with x.com (#3535)
* Replace twitter.com with x.com
2024-07-05 17:26:12 +02:00
Thomas Kaul
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
Chang-Yen Tseng
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
Thomas Kaul
194aee97db
Feature/update development instructions to control flow (#3466) 2024-07-02 11:58:13 +02:00
Thomas Kaul
0f77169952
Fix wording (#3463) 2024-07-01 21:03:15 +02:00
Thomas Kaul
0f8dc62c53
Release 2.92.0 (#3532) 2024-06-30 09:23:03 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
dcec3accf0
Feature/improve caching of benchmarks (#3530)
* Improve caching

* Update changelog
2024-06-29 16:53:35 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
76890e63fa
Bugfix/fix all time high in benchmarks (#3527)
* Fix all time high

* Update changelog
2024-06-29 10:03:45 +02:00
Thomas Kaul
4fb2aebf4f
Release 2.91.0 (#3522) 2024-06-26 20:40:29 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
8b3cc5c11a
Bugfix/fix dialog position on mobile (#3521)
* Fix dialog position on mobile

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

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

* Update changelog
2024-06-24 21:24:03 +02:00
Thomas Kaul
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
Thomas Kaul
c4499df74c
Feature/add wealthy tracker (#3510)
* Add Wealthy Tracker
2024-06-23 10:22:35 +02:00
Thomas Kaul
24bcc15b6a
Release 2.90.0 (#3508) 2024-06-22 09:58:19 +02:00
Eduardo Marinho
ff121243e4
Feature/extend asset profile for currency (#3495)
* Extend asset profile for currency

* Update changelog
2024-06-22 09:54:23 +02:00
Thomas Kaul
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
Thomas Kaul
0780ee4adb
Feature/improve language localization for de 20240620 (#3504)
* Update translations

* Update changelog
2024-06-20 20:45:56 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
a201fc7a97
Feature/upgrade stripe dependencies 20240617 (#3499)
* Upgrade Stripe dependencies

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

* Update changelog
2024-06-18 20:11:49 +02:00
Juan Abascal Sanchez
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
79a7e12a9f
Release 2.89.0 (#3491) 2024-06-14 03:43:47 +02:00
Thomas Kaul
bf20a5de82
Feature/improve date validation in activity endpoints (#3489)
* Improve date validation

* Update changelog
2024-06-14 03:40:47 +02:00
Thomas Kaul
0adefe14e1
Feature/improve language localization (#3487)
* Update translations
2024-06-13 12:08:15 +02:00
Thomas Kaul
f24561cc3d
Feature/improve style of personal finance tools list (#3486) 2024-06-13 12:07:42 +02:00
Eduardo Marinho
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
Thomas Kaul
e5d8faf2dc
Feature/improve language localization for de 20240612 (#3483)
* Update translations

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

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

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

* Update changelog
2024-06-11 19:20:13 +02:00
Thomas Kaul
118e17f78c
Feature/improve wording on allocations page (#3479) 2024-06-11 11:58:26 +02:00
Thomas Kaul
cc92592d86
Feature/refactor personal finance tools section (#3478)
* Refactoring
2024-06-11 11:57:24 +02:00
coharms
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
Thomas Kaul
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
Thomas Kaul
5fc9fde129
Release 2.87.0 (#3473) 2024-06-08 19:33:21 +02:00
Thomas Kaul
00e50c6abe
Feature/improve portfolio summary (#3472)
* Improve portfolio summary

* Update changelog
2024-06-08 19:31:50 +02:00
Thomas Kaul
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
Thomas Kaul
f5e6f7dcfe
Bugfix/fix initialization of fire calculator (#3470)
* Fix initialization

* Update changelog
2024-06-08 18:44:57 +02:00
Thomas Kaul
87501e094d
Feature/improve allocations by ETF holding for mobile (#3469) 2024-06-08 17:20:38 +02:00
Thomas Kaul
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
Thomas Kaul
fc4e6ae6db
Feature/improve language localization for de 20240608 (#3468)
* Update translations

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

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

* Update changelog
2024-06-07 21:45:07 +02:00
Thomas Kaul
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
Thomas Kaul
137e8e090a
Release 2.85.0 (#3461) 2024-06-06 17:25:08 +02:00
Thomas Kaul
6d941500cd
Bugfix/fix default locale in value component (#3454)
* Fix default locale

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

* Add Navexa

* Add Stonksfolio
2024-06-05 17:32:43 +02:00
Eduardo Marinho
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
Thomas Kaul
9f875adf0c
Feature/improve language localization for de 20240602 (#3452)
* Update translations

* Update changelog
2024-06-03 20:26:58 +02:00
Thomas Kaul
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
Thomas Kaul
98a9523eee
Feature/extend personal finance tools (#3448) 2024-06-02 10:20:09 +02:00
Thomas Kaul
dfb3365efb
Release 2.84.0 (#3447) 2024-06-01 11:16:13 +02:00
Thomas Kaul
0e08d8830e
Handle reduce of empty array (#3446) 2024-06-01 11:14:34 +02:00
Thomas Kaul
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
Thomas Kaul
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
Eduardo Marinho
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
Thomas Kaul
b12ac1fe84
Feature/simplify module imports of api (#3443)
* Simplify module imports
2024-06-01 10:02:43 +02:00
Thomas Kaul
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
Thomas Kaul
fb326fe0cc
Release 2.83.0 (#3442) 2024-05-30 20:47:43 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
918d0b85d4
Feature/update passport dependencies (#3433)
* Update passport dependencies

* Refactor Google strategy

* Update changelog
2024-05-28 13:47:45 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
766a2d7c2f
Release 2.82.0 (#3425) 2024-05-22 19:53:20 +02:00
Eduardo Marinho
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
741a0e36d2
Add links to tagged issues (#3405)
* help wanted
* good first issue
2024-05-20 22:28:43 +03:00
Thomas Kaul
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
Thomas Kaul
812ff5cbdc
Feature/refresh cryptocurrencies list 20240514 (#3411)
* Refresh cryptocurrencies list

* Update changelog
2024-05-18 17:40:25 +03:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
5d9c38663d
Feature/migrate various pages to standalone components (#3404)
* Migrate to standalone components

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

* Update changelog
2024-05-12 10:25:59 +02:00
Thomas Kaul
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
Gerard Du Pre
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
Thomas Kaul
782d131b0d
Feature/add indicator for active filters (#3398)
* Add indicator for active filters

* Update changelog
2024-05-12 09:42:27 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
80464c7846
Release 2.80.0 (#3386) 2024-05-08 20:55:39 +02:00
Thomas Kaul
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
Thomas Kaul
127dbf9dcd
Update translations (#3384) 2024-05-08 20:37:53 +02:00
Thomas Kaul
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
Thomas Kaul
4ad4fa2b30
Feature/clean up deprecated GET api/portfolio/positions endpoint (#3373) 2024-05-08 20:04:32 +02:00
Thomas Kaul
1fd836194f
Feature/add absolute change column to holdings table (#3378)
* Add absolute change column

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

* Update changelog
2024-05-07 20:48:02 +02:00
Thomas Kaul
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
Thomas Kaul
9b5e350e3b
Feature/harmonize log message (#3343) 2024-05-06 17:03:58 +02:00
Colin Seymour
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
Thomas Kaul
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
Thomas Kaul
8438a45bcf
Update changelog (#3372) 2024-05-04 16:32:32 +02:00
Thomas Kaul
30a64e7fc1
Release 2.79.0 (#3371) 2024-05-04 15:54:29 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
c1ad483f33
Improve alignment (#3370) 2024-05-04 15:50:47 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
77beaaba08
Refactoring portfolio service (#3365) 2024-05-03 21:48:46 +02:00
Thomas Kaul
d9c07456cd
Release 2.78.0 (#3361) 2024-05-02 20:32:47 +02:00
Thomas Kaul
0a53df4293
Feature/improve inactive user role (#3360)
* Improve inactive role

* Update changelog
2024-05-02 20:31:20 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
261f5844dd
Add type column to README.md (#3295) 2024-04-30 14:08:14 +02:00
Fedron
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
Thomas Kaul
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
Thomas Kaul
d735e4db75
Release 2.77.1 (#3340) 2024-04-27 19:25:23 +02:00
Thomas Kaul
e10707fde4
Add missing guard to fix public page (#3339) 2024-04-27 19:24:08 +02:00
Thomas Kaul
ac953df809
Release 2.77.0 (#3338) 2024-04-27 15:37:48 +02:00
Thomas Kaul
bb4ee50738
Feature/update browserslist database 20240417 (#3288)
* Update the browserslist database

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

* Update changelog
2024-04-27 15:35:28 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
a4efbc0131
Feature/migrate UI components to control flow (#3324)
* Migrate to control flow

* Update changelog
2024-04-26 17:40:00 +02:00
Thomas Kaul
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
Thomas Kaul
46432edce9
Feature/extend faq by custom asset instructions (#3326)
* Add instructions for custom asset

* Update changelog
2024-04-24 20:35:39 +02:00
Thomas Kaul
990028316e
Refactor form controls to form getter (#3325) 2024-04-24 20:20:56 +02:00
Thomas Kaul
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
Thomas Kaul
0fdeef7953
Release 2.76.0 (#3322) 2024-04-23 18:58:43 +02:00
Thomas Kaul
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
Thomas Kaul
a6dde8ad43
Release 2.75.1 (#3317) 2024-04-21 17:14:35 +02:00
Thomas Kaul
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
Thomas Kaul
ab59eb5c92
Release 2.75.0 (#3316) 2024-04-21 10:30:14 +02:00
Thomas Kaul
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
Thomas Kaul
2d70b18593
Remove links (#3306) 2024-04-21 10:08:22 +02:00
Thomas Kaul
b6ea7d23fa
Bugfix/add total value in base currency to redacted values (#3313)
* Add totalValueInBaseCurrency

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

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

* Update changelog
2024-04-20 12:54:06 +02:00
Thomas Kaul
7788b5a987
Improve messages (#3308) 2024-04-20 12:53:51 +02:00
Thomas Kaul
47fd029e0c
Clean up styles (#3305) 2024-04-20 12:15:21 +02:00
Thomas Kaul
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
Bastien Jeannelle
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
Thomas Kaul
22d63c6102
Improve logging (#3304)
* Improve logging
2024-04-20 10:49:18 +02:00
Thomas Kaul
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
Thomas Kaul
bed9ae916c
Migrate UI components to standalone (#3302) 2024-04-19 20:57:47 +02:00
Thomas Kaul
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
Thomas Kaul
ab86dd5318
Add Home Assistant (#3291) 2024-04-19 20:43:52 +02:00
Thomas Kaul
dba73d80a3
Improve logging (#3300) 2024-04-19 20:31:38 +02:00
Thomas Kaul
92fb05320a
Migrate UI components to standalone (#3296) 2024-04-19 18:31:35 +02:00
Thomas Kaul
73d62bb51f
Migrate UI components to standalone (#3290) 2024-04-18 20:46:12 +02:00
Bastien Jeannelle
127b7d4f25
Make pre-commit executable by default (#3283) 2024-04-18 18:39:20 +02:00
Thomas Kaul
e79d607ab8
Release 2.73.0 (#3287) 2024-04-17 17:42:28 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
ff91ed21df
Upgrade @types/lodash to version 4.17.0 (#3227) 2024-04-15 19:25:46 +02:00
Fedron
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
b31bbbe2d1
Release 2.72.0 (#3270) 2024-04-13 09:31:07 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
45340b581f
Bugfix/fix public page by including markets data (#3263)
* Include markets data

* Update changelog
2024-04-12 15:13:34 +02:00
Thomas Kaul
6f8fe45fc2
Update OSS Friends (#3258) 2024-04-11 19:14:56 +02:00
Thomas Kaul
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
Thomas Kaul
2b97bbd05d
Move getChart() to portfolio calculator (#3255) 2024-04-09 13:44:23 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
6e2885ed20
Release 2.71.0 (#3252) 2024-04-07 11:32:55 +02:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
b51255a543
Feature/add key to x ray rule (#3248)
* Add key

* Update changelog
2024-04-07 09:25:14 +02:00
Thomas Kaul
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
Thomas Kaul
ca2e748c56
Bugfix/add missing tags in portfolio calculator (#3243)
* Add missing tags

* Update changelog
2024-04-06 20:03:16 +02:00
sylis
5c480109d5
Feature/add quotes in README.md for API auth documentation (#3246) 2024-04-06 20:00:56 +02:00
Thomas Kaul
6152ff4b44
Remove condition (#3242) 2024-04-06 12:50:35 +02:00
Thomas Kaul
c6641fde36
Feature/add icon to create or update platform dialog (#3241)
* Add platform icon

* Update changelog
2024-04-06 09:11:15 +02:00
Thomas Kaul
4ae7e9fcbe
Feature/add icon to asset profile dialog (#3240)
* Add asset profile icon

* Update changelog
2024-04-06 08:42:06 +02:00
Thomas Kaul
c10ae431a2
Feature/extend faq by data providers (#3239)
* Add data providers

* Update changelog
2024-04-06 08:40:44 +02:00
Thomas Kaul
883e30e451
Feature/improve language localization for de 20240403 (#3236)
* Update translations

* Update changelog
2024-04-05 19:38:10 +02:00
Thomas Kaul
f1f4f6247d
Feature/validate url in create and update platform dto (#3235)
* Validate url

* Update changelog
2024-04-04 09:08:37 +02:00
Fedron
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
Arshad Jamal
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
Thomas Kaul
26b9660e11
Release 2.70.0 (#3234) 2024-04-02 20:29:47 +02:00
Bastien Jeannelle
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
Thomas Kaul
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
Thomas Kaul
efdc9b387f
Eliminate ghostfolio-style.scss (#3228) 2024-04-01 13:24:00 +02:00
Thomas Kaul
d7b579e3e8
Feature/refactor getAnnualizedPerformancePercent to portfolio calculator (#3226)
* Move getAnnualizedPerformancePercent() to portfolio calculator
2024-04-01 10:42:15 +02:00
Thomas Kaul
b8533050b0
Bugfix/fix duplicated tags in position detail dialog (#3224)
* Fix duplicated tags

* Update changelog
2024-04-01 09:22:35 +02:00
Thomas Kaul
1b81409b35
Add OpenAlternative logo (#3225) 2024-04-01 09:02:10 +02:00
Francisco Silva
8cd6c34ed8
Feature/introduce portfolio calculator factory (#3214)
* Introduce portfolio calculator factory

* Update changelog
2024-03-31 20:07:58 +02:00
Thomas Kaul
0c68474802
Extract locales (#3223) 2024-03-31 11:41:44 +02:00
Thomas Kaul
34997f91db
Feature/setup webpack bundle analyzer (#3222)
* Set up  Webpack Bundle Analyzer

* Update changelog
2024-03-31 11:38:09 +02:00
Thomas Kaul
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
qiaoy
af47889d65
Add Chinese translations (#3215)
* Add Chinese translations
2024-03-31 11:24:32 +02:00
Thomas Kaul
51203ec96e
Feature/setup chinese (#3220)
* Set up chinese

* Update changelog
2024-03-31 10:46:30 +02:00
Thomas Kaul
a2277dea2c
Release 2.69.0 (#3212) 2024-03-30 13:43:43 +01:00
Bastien Jeannelle
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
Thomas Kaul
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
Thomas Kaul
5ffc39c32f
Feature/improve usability to delete asset profile (#3208)
* Disable delete button for benchmarks

* Update changelog
2024-03-30 11:23:59 +01:00
Fedron
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
Thomas Kaul
0581b8b9ec
Release 2.68.0 (#3205) 2024-03-29 17:54:17 +01:00
Thomas Kaul
63a61fb492
Move portfolio calculator (#3204)
* Move portfolio calculator
2024-03-29 17:47:44 +01:00
Thomas Kaul
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
Thomas Kaul
5529fdc0ee
Move transaction points to constructor (#3202) 2024-03-29 13:50:24 +01:00
Thomas Kaul
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
Fedron
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
helgehatt
b41eb60348
Fix chart tooltip of benchmark comparator (#3167)
* Fix chart tooltip of benchmark comparator

* Update changelog
2024-03-28 18:23:56 +01:00
Christoph Califice
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
Thomas Kaul
e3abe4feee
Release 2.67.0 (#3197) 2024-03-26 17:45:32 +01:00
Thomas Kaul
50391e199a
Feature/improve generation of random strings (#3196)
* Replace Math.random() with crypto.randomBytes()

* Update changelog
2024-03-26 17:43:42 +01:00
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
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
Thomas Kaul
458ef169f4
Feature/add support for toncoin cryptocurrency (#3189)
* Add Toincoin

* Update changelog
2024-03-24 09:50:00 +01:00
Thomas Kaul
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
Thomas Kaul
43e9528d8c
Release 2.66.3 (#3188) 2024-03-23 20:56:22 +01:00
Thomas Kaul
522c54c9b4
Release 2.66.2 (#3187) 2024-03-23 20:40:09 +01:00
996 changed files with 129705 additions and 129880 deletions

View File

@ -1,7 +1,7 @@
COMPOSE_PROJECT_NAME=ghostfolio
# CACHE
REDIS_HOST=localhost
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=<INSERT_REDIS_PASSWORD>
@ -12,5 +12,5 @@ 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

@ -1,39 +0,0 @@
name: Build code
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node_version:
- 18
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Check formatting
run: yarn format:check
- name: Execute tests
run: yarn test
- name: Build application
run: yarn 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,35 @@ 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

7
.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
@ -28,15 +30,14 @@
.env
.env.prod
.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
v20

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,53 @@
# Ghostfolio Development Guide
## Development Environment
### Prerequisites
- [Docker](https://www.docker.com/products/docker-desktop)
- [Node.js](https://nodejs.org/en/download) (version 20+)
- 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
Run `npm run start:client` and open https://localhost:4200/en 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 +58,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 +82,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,69 +1,67 @@
FROM --platform=$BUILDPLATFORM node:18-slim as builder
FROM --platform=$BUILDPLATFORM node:20-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
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
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:20-slim
LABEL org.opencontainers.image.source="https://github.com/ghostfolio/ghostfolio"
ENV NODE_ENV=production
# Add tini, which is an init process that handles signaling within the container
# and with the host. See https://github.com/krallin/tini
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
RUN apt-get update && apt-get install -y --no-install-suggests \
curl \
openssl \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /ghostfolio/dist/apps /ghostfolio/apps
COPY ./docker/entrypoint.sh /entrypoint.sh
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 [ "entrypoint.sh" ]
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) | [**X**](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 [CasaOS](https://github.com/bigbeartechworld/big-bear-casaos), [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.dev` to `.env` and populate it with your data (`cp .env.dev .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

@ -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
) {}
@ -24,32 +36,114 @@ export class AccountBalanceService {
});
}
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';
@ -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
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,7 +2,9 @@ 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 {
@ -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
@ -70,27 +74,33 @@ export class AccountController {
);
}
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
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Query('dataSource') filterByDataSource?: string,
@Query('symbol') filterBySymbol?: string
): Promise<Accounts> {
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
});
@ -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
});
}

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
) {}
@ -51,13 +62,19 @@ export class AccountService {
orderBy?: Prisma.AccountOrderByWithRelationInput;
}): Promise<
(Account & {
balances?: AccountBalance[];
Order?: Order[];
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,27 +104,38 @@ 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[]> {
@ -153,12 +183,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 +222,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 +273,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,23 +1,23 @@
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 { 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,
AdminUsers,
EnhancedSymbolProfile
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
@ -82,19 +82,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 +109,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 +142,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,6 +214,9 @@ export class AdminController {
});
}
/**
* @deprecated
*/
@Get('market-data/:dataSource/:symbol')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@ -235,14 +243,19 @@ export class AdminController {
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);
}
}
/**
* @deprecated
*/
@HasPermission(permissions.accessAdminControl)
@Post('market-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@ -321,15 +334,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)
@ -341,4 +353,17 @@ export class AdminController {
) {
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,14 @@
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 { 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 +20,18 @@ import { QueueModule } from './queue/queue.module';
@Module({
imports: [
ApiModule,
BenchmarkModule,
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
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 { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { MarketDataPreset } 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({
@ -88,41 +107,44 @@ 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(
PROPERTY_CURRENCIES
)) as string[];
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;
const [settings, transactionCount, userCount] = await Promise.all([
this.propertyService.get(),
this.prismaService.order.count(),
this.countUsersWithAnalytics()
]);
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(),
return {
settings,
transactionCount,
userCount,
version: environment.version
};
}
@ -146,7 +168,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' ||
@ -196,101 +227,199 @@ export class AdminService {
}
}
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: { Order: true }
},
assetClass: true,
assetSubClass: true,
comment: true,
countries: true,
currency: true,
dataSource: true,
id: true,
isActive: true,
isUsedByUsersWithSubscription: true,
name: true,
Order: {
orderBy: [{ date: 'asc' }],
select: { date: true },
take: 1
},
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,
assetClass,
assetSubClass,
comment,
countries,
currency,
dataSource,
id,
isActive,
isUsedByUsersWithSubscription,
name,
Order,
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.Order,
date: Order?.[0]?.date,
isUsedByUsersWithSubscription: await isUsedByUsersWithSubscription
};
}
)
);
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 +438,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 +612,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: {
Order: {
where: {
User: {
subscriptions: {
some: {
expiresAt: {
gt: new Date()
}
}
}
}
}
}
}
}
},
where: {
id
}
});
return _count.Order > 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,30 +739,44 @@ 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
};
});
}
);
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'
lastRequestAt: 'desc'
}
};
where = {
@ -426,6 +788,8 @@ export class AdminService {
const usersWithAnalytics = await this.prismaService.user.findMany({
orderBy,
skip,
take,
where,
select: {
_count: {
@ -435,33 +799,40 @@ export class AdminService {
select: {
activityCount: true,
country: true,
dataProviderGhostfolioDailyRequests: true,
updatedAt: true
}
},
createdAt: true,
id: true,
role: true,
Subscription: true
},
take: 30
subscriptions: {
orderBy: {
expiresAt: 'desc'
},
take: 1,
where: {
expiresAt: {
gt: new Date()
}
}
}
}
});
return usersWithAnalytics.map(
({ _count, Analytics, createdAt, id, role, Subscription }) => {
({ _count, Analytics, createdAt, id, role, subscriptions }) => {
const daysSinceRegistration =
differenceInDays(new Date(), createdAt) + 1;
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,
@ -471,6 +842,7 @@ export class AdminService {
subscription,
accountCount: _count.Account || 0,
country: Analytics?.country,
dailyApiRequests: Analytics?.dataProviderGhostfolioDailyRequests || 0,
lastActivity: Analytics?.updatedAt,
transactionCount: _count.Order || 0
};

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,10 +1,12 @@
import { EventsModule } from '@ghostfolio/api/events/events.module';
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 { 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 { PortfolioSnapshotQueueModule } from '@ghostfolio/api/services/queues/portfolio-snapshot/portfolio-snapshot.module';
import { TwitterBotModule } from '@ghostfolio/api/services/twitter-bot/twitter-bot.module';
import {
DEFAULT_LANGUAGE_CODE,
@ -14,6 +16,7 @@ import {
import { BullModule } from '@nestjs/bull';
import { Module } 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 +26,19 @@ 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 { 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';
@ -40,17 +52,21 @@ 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),
@ -64,18 +80,24 @@ import { UserModule } from './user/user.module';
ConfigurationModule,
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({
@ -105,11 +127,11 @@ import { UserModule } from './user/user.module';
SitemapModule,
SubscriptionModule,
SymbolModule,
TagModule,
TagsModule,
TwitterBotModule,
UserModule
UserModule,
WatchlistModule
],
controllers: [AppController],
providers: [CronService]
})
export class AppModule {}

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,76 @@
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 ' },
true,
async (apiKey: string, done: (error: any, user?: any) => void) => {
try {
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 }
});
}
done(null, user);
} catch (error) {
done(error, null);
}
}
);
}
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

@ -2,10 +2,12 @@ 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 { 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,6 +31,13 @@ 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;
@ -37,7 +46,7 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
update: {
country,
activityCount: { increment: 1 },
updatedAt: new Date()
lastRequestAt: new Date()
},
where: { userId: user.id }
});
@ -45,10 +54,20 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
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,
@ -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,7 +129,7 @@ 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 } }
});
@ -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,65 @@
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 {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE
} from '@ghostfolio/common/config';
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 ?? DEFAULT_LANGUAGE_CODE,
userCurrency:
this.request.user.Settings.settings.baseCurrency ?? DEFAULT_CURRENCY,
userId: this.request.user.id
});
return { prompt };
}
}

View File

@ -0,0 +1,53 @@
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 { 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 { 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 { 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,
ConfigurationModule,
DataProviderModule,
ExchangeRateDataModule,
ImpersonationModule,
MarketDataModule,
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule,
UserModule
],
providers: [
AccountBalanceService,
AccountService,
AiService,
CurrentRateService,
MarketDataService,
PortfolioCalculatorFactory,
PortfolioService,
RulesService
]
})
export class AiModule {}

View File

@ -0,0 +1,71 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { Filter } from '@ghostfolio/common/interfaces';
import type { AiPromptMode } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AiService {
public constructor(private readonly portfolioService: PortfolioService) {}
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,63 @@
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 { 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,
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,414 @@
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
);
}
}
/**
* @deprecated
*/
@Get('dividends/:symbol')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getDividendsV1(
@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('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
);
}
}
/**
* @deprecated
*/
@Get('historical/:symbol')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getHistoricalV1(
@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('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
);
}
}
/**
* @deprecated
*/
@Get('lookup')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async lookupSymbolV1(
@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: 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('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
);
}
}
/**
* @deprecated
*/
@Get('quotes')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getQuotesV1(
@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('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
);
}
}
/**
* @deprecated
*/
@Get('status')
@HasPermission(permissions.enableDataProviderGhostfolio)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getStatusV1(): Promise<DataProviderGhostfolioStatusResponse> {
return this.ghostfolioService.getStatus({ user: this.request.user });
}
@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,344 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.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(
PROPERTY_DATA_SOURCES_GHOSTFOLIO_DATA_PROVIDER_MAX_REQUESTS
)) as string) || '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 {
return {
isPremium: false,
name: 'Ghostfolio Premium',
url: 'https://ghostfol.io'
};
}
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) {
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,49 @@
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 { 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 { 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,
DataProviderModule,
ExchangeRateDataModule,
ImpersonationModule,
MarketDataModule,
OrderModule,
PortfolioSnapshotQueueModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule,
TransformDataSourceInRequestModule,
UserModule
],
providers: [
AccountBalanceService,
AccountService,
CurrentRateService,
PortfolioCalculatorFactory,
PortfolioService,
RulesService
]
})
export class PublicModule {}

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,85 @@
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 { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
Delete,
Get,
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(
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly watchlistService: WatchlistService
) {}
@Post()
@HasPermission(permissions.createWatchlistItem)
@UseGuards(AuthGuard('jwt'))
@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 watchlistItem = await this.watchlistService
.getWatchlistItems(this.request.user.id)
.then((items) => {
return items.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(): Promise<AssetProfileIdentifier[]> {
return this.watchlistService.getWatchlistItems(this.request.user.id);
}
}

View File

@ -0,0 +1,19 @@
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 { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { Module } from '@nestjs/common';
import { WatchlistController } from './watchlist.controller';
import { WatchlistService } from './watchlist.service';
@Module({
controllers: [WatchlistController],
imports: [
PrismaModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
],
providers: [WatchlistService]
})
export class WatchlistModule {}

View File

@ -0,0 +1,79 @@
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { Injectable, NotFoundException } from '@nestjs/common';
import { DataSource } from '@prisma/client';
@Injectable()
export class WatchlistService {
public constructor(private readonly prismaService: PrismaService) {}
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) {
throw new NotFoundException(
`Asset profile not found for ${symbol} (${dataSource})`
);
}
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<AssetProfileIdentifier[]> {
const user = await this.prismaService.user.findUnique({
select: {
watchlist: {
select: { dataSource: true, symbol: true }
}
},
where: { id: userId }
});
return user.watchlist ?? [];
}
}

View File

@ -21,10 +21,11 @@ export class ExportController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async export(
@Query('accounts') filterByAccounts?: string,
@Query('activityIds') activityIds?: string[],
@Query('activityIds') filterByActivityIds?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('tags') filterByTags?: string
): Promise<Export> {
const activityIds = filterByActivityIds?.split(',') ?? [];
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,

View File

@ -1,10 +1,7 @@
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 { 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,15 +9,7 @@ import { ExportController } from './export.controller';
import { ExportService } from './export.service';
@Module({
imports: [
AccountModule,
ApiModule,
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
OrderModule,
RedisCacheModule
],
imports: [AccountModule, ApiModule, OrderModule, TagModule],
controllers: [ExportController],
providers: [ExportService]
})

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: 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,16 @@
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 {
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,7 +20,21 @@ 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) {

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';
@ -98,12 +98,9 @@ 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
symbol
});
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,8 +26,8 @@ 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 { Big } from 'big.js';
import { endOfToday, isAfter, isSameSecond, parseISO } from 'date-fns';
import { uniqBy } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
@ -39,7 +38,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,
@ -48,9 +46,8 @@ export class ImportService {
public async getDividends({
dataSource,
symbol,
userCurrency
}: UniqueAsset & { userCurrency: string }): Promise<Activity[]> {
symbol
}: AssetProfileIdentifier): Promise<Activity[]> {
try {
const { firstBuyDate, historicalData, orders } =
await this.portfolioService.getPosition(dataSource, undefined, symbol);
@ -71,65 +68,68 @@ export class ImportService {
})
]);
const accounts = orders.map((order) => {
return order.Account;
});
const accounts = orders
.filter(({ Account }) => {
return !!Account;
})
.map(({ Account }) => {
return 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;
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 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 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 error: ActivityError = isDuplicate
? { code: 'IS_DUPLICATE' }
: undefined;
const error: ActivityError = isDuplicate
? { code: 'IS_DUPLICATE' }
: 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 {
Account,
date,
error,
quantity,
value,
assetProfile.currency,
userCurrency
)
};
});
accountId: Account?.id,
accountUserId: undefined,
comment: undefined,
currency: undefined,
createdAt: undefined,
fee: 0,
feeInAssetProfileCurrency: 0,
id: assetProfile.id,
isDraft: false,
SymbolProfile: assetProfile,
symbolProfileId: assetProfile.id,
type: 'DIVIDEND',
unitPrice: marketPrice,
unitPriceInAssetProfileCurrency: marketPrice,
updatedAt: undefined,
userId: Account?.userId
};
})
);
} catch {
return [];
}
@ -214,7 +214,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 =
@ -256,27 +256,24 @@ export class ImportService {
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 +282,14 @@ export class ImportService {
assetSubClass,
countries,
createdAt,
currency,
cusip,
dataSource,
figi,
figiComposite,
figiShareClass,
holdings,
id,
isActive,
isin,
name,
scraperConfiguration,
@ -310,43 +309,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 +329,14 @@ export class ImportService {
assetSubClass,
countries,
createdAt,
currency,
cusip,
dataSource,
figi,
figiComposite,
figiShareClass,
holdings,
id,
isActive,
isin,
name,
scraperConfiguration,
@ -371,9 +345,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 +360,7 @@ export class ImportService {
order = await this.orderService.createOrder({
comment,
currency,
date,
fee,
quantity,
@ -394,9 +370,10 @@ export class ImportService {
SymbolProfile: {
connectOrCreate: {
create: {
currency,
dataSource,
symbol
symbol,
currency: assetProfile.currency,
userId: dataSource === 'MANUAL' ? user.id : undefined
},
where: {
dataSource_symbol: {
@ -410,6 +387,11 @@ export class ImportService {
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 +400,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 +418,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 +442,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 +467,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 +487,7 @@ export class ImportService {
return {
accountId,
comment,
currency,
date,
error,
fee,
@ -518,25 +495,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 +540,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 +583,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[] = [];
@ -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()
};
@ -142,7 +138,7 @@ export class InfoService {
},
{
Analytics: {
updatedAt: {
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) {
@ -314,19 +295,6 @@ 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 {
@ -334,27 +302,22 @@ export class InfoService {
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) {

View File

@ -1,4 +1,4 @@
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 {
Controller,
@ -26,12 +26,13 @@ export class LogoController {
@Res() response: Response
) {
try {
const buffer = await this.logoService.getLogoByDataSourceAndSymbol({
dataSource,
symbol
});
const { buffer, type } =
await this.logoService.getLogoByDataSourceAndSymbol({
dataSource,
symbol
});
response.contentType('image/png');
response.contentType(type);
response.send(buffer);
} catch {
response.status(HttpStatus.NOT_FOUND).send();
@ -44,9 +45,9 @@ export class LogoController {
@Res() response: Response
) {
try {
const buffer = await this.logoService.getLogoByUrl(url);
const { buffer, type } = await this.logoService.getLogoByUrl(url);
response.contentType('image/png');
response.contentType(type);
response.send(buffer);
} catch {
response.status(HttpStatus.NOT_FOUND).send();

View File

@ -1,3 +1,4 @@
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
@ -8,7 +9,11 @@ import { LogoService } from './logo.service';
@Module({
controllers: [LogoController],
imports: [ConfigurationModule, SymbolProfileModule],
imports: [
ConfigurationModule,
SymbolProfileModule,
TransformDataSourceInRequestModule
],
providers: [LogoService]
})
export class LogoModule {}

View File

@ -1,10 +1,9 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { HttpException, Injectable } from '@nestjs/common';
import { DataSource } from '@prisma/client';
import got from 'got';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Injectable()
@ -17,7 +16,7 @@ export class LogoService {
public async getLogoByDataSourceAndSymbol({
dataSource,
symbol
}: UniqueAsset) {
}: AssetProfileIdentifier) {
if (!DataSource[dataSource]) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
@ -29,7 +28,7 @@ export class LogoService {
{ dataSource, symbol }
]);
if (!assetProfile) {
if (!assetProfile?.url) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
@ -39,24 +38,26 @@ export class LogoService {
return this.getBuffer(assetProfile.url);
}
public async getLogoByUrl(aUrl: string) {
public getLogoByUrl(aUrl: string) {
return this.getBuffer(aUrl);
}
private getBuffer(aUrl: string) {
const abortController = new AbortController();
setTimeout(() => {
abortController.abort();
}, this.configurationService.get('REQUEST_TIMEOUT'));
return got(
private async getBuffer(aUrl: string) {
const blob = await fetch(
`https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${aUrl}&size=64`,
{
headers: { 'User-Agent': 'request' },
// @ts-ignore
signal: abortController.signal
signal: AbortSignal.timeout(
this.configurationService.get('REQUEST_TIMEOUT')
)
}
).buffer();
).then((res) => res.blob());
return {
buffer: await blob.arrayBuffer().then((arrayBuffer) => {
return Buffer.from(arrayBuffer);
}),
type: blob.type
};
}
}

View File

@ -1,3 +1,6 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { IsAfter1970Constraint } from '@ghostfolio/common/validator-constraints/is-after-1970';
import {
AssetClass,
AssetSubClass,
@ -10,12 +13,12 @@ import {
IsArray,
IsBoolean,
IsEnum,
IsISO4217CurrencyCode,
IsISO8601,
IsNumber,
IsOptional,
IsString,
Min
Min,
Validate
} from 'class-validator';
import { isString } from 'lodash';
@ -24,12 +27,12 @@ export class CreateOrderDto {
@IsString()
accountId?: string;
@IsOptional()
@IsEnum(AssetClass, { each: true })
@IsOptional()
assetClass?: AssetClass;
@IsOptional()
@IsEnum(AssetSubClass, { each: true })
@IsOptional()
assetSubClass?: AssetSubClass;
@IsOptional()
@ -39,14 +42,19 @@ export class CreateOrderDto {
)
comment?: string;
@IsISO4217CurrencyCode()
@IsCurrencyCode()
currency: string;
@IsCurrencyCode()
@IsOptional()
customCurrency?: string;
@IsEnum(DataSource)
@IsOptional()
@IsEnum(DataSource, { each: true })
dataSource?: DataSource;
@IsISO8601()
@Validate(IsAfter1970Constraint)
date: string;
@IsNumber()

View File

@ -1,16 +1,22 @@
import { OrderWithAccount } from '@ghostfolio/common/types';
import { EnhancedSymbolProfile } from '@ghostfolio/common/interfaces';
import { AccountWithPlatform } from '@ghostfolio/common/types';
import { Order, Tag } from '@prisma/client';
export interface Activities {
activities: Activity[];
count: number;
}
export interface Activity extends OrderWithAccount {
export interface Activity extends Order {
Account?: AccountWithPlatform;
error?: ActivityError;
feeInBaseCurrency: number;
feeInAssetProfileCurrency: number;
SymbolProfile?: EnhancedSymbolProfile;
tags?: Tag[];
unitPriceInAssetProfileCurrency: number;
updateAccountBalance?: boolean;
value: number;
valueInBaseCurrency: number;
}
export interface ActivityError {

View File

@ -1,14 +1,18 @@
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 { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-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 { 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 { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { DataGatheringService } from '@ghostfolio/api/services/queues/data-gathering/data-gathering.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
HEADER_KEY_IMPERSONATION
} from '@ghostfolio/common/config';
import { permissions } from '@ghostfolio/common/permissions';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
@ -32,7 +36,7 @@ import { parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { CreateOrderDto } from './create-order.dto';
import { Activities } from './interfaces/activities.interface';
import { Activities, Activity } from './interfaces/activities.interface';
import { OrderService } from './order.service';
import { UpdateOrderDto } from './update-order.dto';
@ -49,22 +53,33 @@ export class OrderController {
@Delete()
@HasPermission(permissions.deleteOrder)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteOrders(): Promise<number> {
public async deleteOrders(
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('tags') filterByTags?: string
): Promise<number> {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByTags
});
return this.orderService.deleteOrders({
filters,
userId: this.request.user.id
});
}
@Delete(':id')
@HasPermission(permissions.deleteOrder)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteOrder(@Param('id') id: string): Promise<OrderModel> {
const order = await this.orderService.order({ id });
const order = await this.orderService.order({
id,
userId: this.request.user.id
});
if (
!hasPermission(this.request.user.permissions, permissions.deleteOrder) ||
!order ||
order.userId !== this.request.user.id
) {
if (!order) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
@ -79,20 +94,33 @@ export class OrderController {
@Get()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getAllOrders(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('range') dateRange?: DateRange,
@Query('skip') skip?: number,
@Query('sortColumn') sortColumn?: string,
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
@Query('symbol') filterBySymbol?: string,
@Query('tags') filterByTags?: string,
@Query('take') take?: number
): Promise<Activities> {
let endDate: Date;
let startDate: Date;
if (dateRange) {
({ endDate, startDate } = getIntervalFromDateRange(dateRange));
}
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByDataSource,
filterBySymbol,
filterByTags
});
@ -101,9 +129,11 @@ export class OrderController {
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const { activities, count } = await this.orderService.getOrders({
endDate,
filters,
sortColumn,
sortDirection,
startDate,
userCurrency,
includeDrafts: true,
skip: isNaN(skip) ? undefined : skip,
@ -115,18 +145,59 @@ export class OrderController {
return { activities, count };
}
@Get(':id')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getOrderById(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@Param('id') id: string
): Promise<Activity> {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(impersonationId);
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const { activities } = await this.orderService.getOrders({
userCurrency,
userId: impersonationUserId || this.request.user.id,
withExcludedAccounts: true
});
const activity = activities.find((activity) => {
return activity.id === id;
});
if (!activity) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return activity;
}
@HasPermission(permissions.createOrder)
@Post()
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async createOrder(@Body() data: CreateOrderDto): Promise<OrderModel> {
const currency = data.currency;
const customCurrency = data.customCurrency;
if (customCurrency) {
data.currency = customCurrency;
delete data.customCurrency;
}
const order = await this.orderService.createOrder({
...data,
date: parseISO(data.date),
SymbolProfile: {
connectOrCreate: {
create: {
currency: data.currency,
currency,
dataSource: data.dataSource,
symbol: data.symbol
},
@ -145,13 +216,16 @@ export class OrderController {
if (data.dataSource && !order.isDraft) {
// Gather symbol data in the background, if data source is set
// (not MANUAL) and not draft
this.dataGatheringService.gatherSymbols([
{
dataSource: data.dataSource,
date: order.date,
symbol: data.symbol
}
]);
this.dataGatheringService.gatherSymbols({
dataGatheringItems: [
{
dataSource: data.dataSource,
date: order.date,
symbol: data.symbol
}
],
priority: DATA_GATHERING_QUEUE_PRIORITY_HIGH
});
}
return order;
@ -176,8 +250,16 @@ export class OrderController {
const date = parseISO(data.date);
const accountId = data.accountId;
const customCurrency = data.customCurrency;
delete data.accountId;
if (customCurrency) {
data.currency = customCurrency;
delete data.customCurrency;
}
return this.orderService.updateOrder({
data: {
...data,

View File

@ -2,14 +2,15 @@ import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/accou
import { AccountService } from '@ghostfolio/api/app/account/account.service';
import { CacheModule } from '@ghostfolio/api/app/cache/cache.module';
import { RedisCacheModule } from '@ghostfolio/api/app/redis-cache/redis-cache.module';
import { UserModule } from '@ghostfolio/api/app/user/user.module';
import { RedactValuesInResponseModule } from '@ghostfolio/api/interceptors/redact-values-in-response/redact-values-in-response.module';
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module';
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module';
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 { 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';
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';
@ -23,15 +24,16 @@ import { OrderService } from './order.service';
imports: [
ApiModule,
CacheModule,
ConfigurationModule,
DataGatheringModule,
DataProviderModule,
ExchangeRateDataModule,
ImpersonationModule,
PrismaModule,
RedactValuesInResponseModule,
RedisCacheModule,
SymbolProfileModule,
UserModule
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
],
providers: [AccountBalanceService, AccountService, OrderService]
})

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