Compare commits

...

1149 Commits

Author SHA1 Message Date
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
Thomas Kaul
0004ced4e1
Release 2.66.1 (#3186) 2024-03-23 20:26:10 +01:00
Thomas Kaul
274c60e961
Release 2.66.0 (#3185) 2024-03-23 19:55:17 +01:00
Bastien Jeannelle
754e98099c
Feature/Set up tini (#3168)
* Set up tini to avoid zombie processes and perform signal forwarding in docker image

* Update changelog
2024-03-23 19:53:27 +01:00
Thomas Kaul
87bf8df1c3
Bugfix/missing performance chart in presenter view (#3181)
* Fix missing performance chart in presenter view

* Update changelog
2024-03-23 19:41:28 +01:00
Thomas Kaul
3f7d6b25c7
Feature/extend faq by backup strategy (#3180)
* Add backup strategy

* Update changelog
2024-03-23 19:10:23 +01:00
Thomas Kaul
8a062e03ab
Feature/add benchmark name to tooltip of benchmark comparator (#3177)
* Add benchmark name to chart tooltip

* Update changelog
2024-03-23 09:38:35 +01:00
Thomas Kaul
a70f45cbf3
Feature/add index for data source symbol to market data table (#3179)
* Add index

* Update changelog
2024-03-23 09:23:59 +01:00
Thomas Kaul
f268264c46
Feature/upgrade nx to version 18.1.2 (#3174)
* Upgrade angular and Nx dependencies

* Update changelog
2024-03-23 09:09:10 +01:00
Thomas Kaul
bbe5d70720
Rename Twitter to X (#3009) 2024-03-21 15:10:35 +01:00
Thomas Kaul
f1d2a52cba
Release 2.65.0 (#3170) 2024-03-19 20:13:22 +01:00
Miguel Coxo
87cc887865
Feature/Set meta theme color dynamically to respect appearance (#3129)
* Set meta theme color dynamically to respect appearance (dark mode)

* Update changelog
2024-03-19 20:11:34 +01:00
Thomas Kaul
61ecd15f1d
Feature/change edit button to a in admin market data page (#3164)
* Change edit button to a

* Update changelog
2024-03-19 19:55:08 +01:00
Thomas Kaul
eb853f05ae
Feature/add support to delete asset profile from dialog (#3165)
* Add support to delete asset profile from dialog

* Update changelog
2024-03-19 19:27:16 +01:00
Thomas Kaul
6285417903
Feature/change grant private access with permissions to general availability (#3169)
* Change grant private access with permissions from experimental to general availability

* Update changelog
2024-03-19 19:25:43 +01:00
Thomas Kaul
ca674a654e
Feature/add symbol and isin to position detail dialog (#3163)
* Add symbol and ISIN

* Update translations

* Update changelog
2024-03-18 13:58:46 +01:00
Thomas Kaul
2729c5651f
Release 2.64.0 (#3161) 2024-03-16 19:00:50 +01:00
Thomas Kaul
7e28e42995
Feature/exclude fees from holdings (#3160) 2024-03-16 18:59:23 +01:00
Thomas Kaul
e21563d903
Feature/increase timeout to load benchmarks (#3158)
* Increase request timeout

* Update changelog
2024-03-16 18:15:34 +01:00
Thomas Kaul
3ede69650c
Feature/upgrade prisma to version 5.11.0 (#3159)
* Upgrade prisma to version 5.11.0

* Update changelog
2024-03-16 16:51:07 +01:00
Gerard Du Pre
c289793c6d
Feature/switch between active and closed holdings (#3146)
* Switch between active and closed holdings on the portfolio holdings page

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-03-16 14:20:58 +01:00
Thomas Kaul
a90c067da0
Clean up (#3157) 2024-03-16 13:40:23 +01:00
Thomas Kaul
38c2baf943
Feature/improve exception handling of current investments in various rules (#3156)
* Improve exception handling

* Update changelog
2024-03-16 12:29:21 +01:00
Thomas Kaul
82c78cad6b
Format (#3155) 2024-03-16 12:28:58 +01:00
Thomas Kaul
bffe6060bd
Pass portfolio calculator to getChart() (#3153) 2024-03-16 10:05:16 +01:00
Thomas Kaul
841bd5c33f
Bugfix/fix dividend accumulation in symbol metrics (#3152)
* Fix total dividend calculation

* Update changelog
2024-03-16 10:04:57 +01:00
rodriguestiago0
3b895afc9e
Enable account balance update for fee and interest activities (#3145)
* Enable account balance update for fee and interest activities

* Update changelog
2024-03-15 18:56:17 +01:00
Thomas Kaul
00c2ede85e
Feature/improve usability of platform and tag management (#3144)
* Improve usability

* Update changelog
2024-03-15 08:37:41 +01:00
Thomas Kaul
8420cb830c
Feature/add product roadmap to faq (#3143)
* Add product roadmap

* Update changelog
2024-03-14 14:09:20 +01:00
helgehatt
a0ddd1f9b9
Fix date conversion in import of historical market data (#3117)
* Fix date conversion in import of historical market data

* Update changelog
2024-03-13 20:44:33 +01:00
Thomas Kaul
40d93066ff
Introduce .env.dev (#3120) 2024-03-13 20:21:54 +01:00
Thomas Kaul
671e4e316b
Release 2.63.2 (#3138) 2024-03-12 20:50:32 +01:00
Thomas Kaul
473136e9aa
Release 2.63.1 (#3135)
* Release 2.63.1
2024-03-11 21:35:43 +01:00
Thomas Kaul
9a3db91982
Release 2.63.0 (#3134) 2024-03-11 20:19:23 +01:00
Thomas Kaul
d23cb5f190
Feature/upgrade simplewebauthn dependencies to version 9.0 (#3130)
* Upgrade @simplewebauthn/browser and @simplewebauthn/server to version 9.0

* Update changelog
2024-03-11 20:17:47 +01:00
Thomas Kaul
7a364472c8
Bugfix/fix liability issue in allocations (#3133)
* Remove liabilities from allocations calculation

* Update changelog
2024-03-11 20:16:56 +01:00
Thomas Kaul
59c064e3c8
Feature/upgrade yahoo finance2 to version 2.10.0 (#3127)
* Upgrade yahoo-finance2 to version 2.10.0

* Update changelog
2024-03-11 20:15:55 +01:00
Thomas Kaul
e792924606
Update OSS friends (#3132) 2024-03-11 20:15:32 +01:00
Thomas Kaul
d32dd5e860
Feature/upgrade countries list to version 3.1.0 (#3131)
* Upgrade countries-list to version 3.1.0

* Update changelog
2024-03-11 19:16:20 +01:00
Thomas Kaul
bb86f85203
Feature/add available home server systems to faq (#3126)
* Add available home server systems

* Update changelog

* Add CasaOS to README.md
2024-03-10 09:50:43 +01:00
gizmodus
0bca8897d6
Fix average price calculation by only considering BUY transactions (#3125)
* Fix average price calculation by only considering buy transactions

* Update changelog
2024-03-10 09:35:47 +01:00
Thomas Kaul
ba73f6de2e
Release 2.62.0 (#3124) 2024-03-09 19:57:56 +01:00
Thomas Kaul
eb75be8535
Optimize details endpoint (#3123)
* Make summary optional

* Introduce dedicated holdings endpoint

* Update changelog
2024-03-09 19:56:26 +01:00
Thomas Kaul
6d2a897366
Refactor orders with activities (#3122) 2024-03-09 17:17:52 +01:00
Thomas Kaul
d8bfb23f20
Refactor reduce() with getSum() (#3121) 2024-03-09 16:53:59 +01:00
Gerard Du Pre
d9d71e7827
Fix issue with removing account from activity (#3112)
* Fix issue with removing account from activity

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-03-09 15:52:05 +01:00
Thomas Kaul
b642ce08e5
Refactor item type (#3119) 2024-03-09 12:32:56 +01:00
Thomas Kaul
bc8d8309d4
Improve handling of future liabilities (#3118)
* Improve handling of future liabilities

* Refactor currentValue to currentValueInBaseCurrency

* Update changelog
2024-03-09 11:07:01 +01:00
Thomas Kaul
1f2f9f22f2
Feature/remove environment variable web auth rp (#3115)
* Remove environment variable WEB_AUTH_RP_ID

* Update changelog
2024-03-08 19:00:21 +01:00
Thomas Kaul
7a3237f1ff
Adapt style of inactive users (#3114) 2024-03-08 18:59:23 +01:00
Thomas Kaul
07661d9262
Feature/integrate dividend into transaction point concept (#3092)
* Integrate dividend into transaction point concept

* Update changelog
2024-03-07 20:07:50 +01:00
Gerard Du Pre
77358eed65
Feature/Include user role in admin endpoint (#3107)
* Include user role in admin endpoint
2024-03-07 19:38:57 +01:00
Thomas Kaul
c641c28b12
Release 2.61.1 (#3110) 2024-03-06 22:08:00 +01:00
Thomas Kaul
c54392b7bb
Bugfix/fix exception in account value calculation (#3109)
* Fix exception in value of account calculation caused by liabilities

* Update changelog
2024-03-06 22:06:27 +01:00
Gerard Du Pre
f3a8822a77
Feature/remove-v-from-version-in-admin-endpoint (#3101)
* Remove "v" from version in admin endpoint
2024-03-06 11:11:10 +01:00
Thomas Kaul
f1dc075c36
Update translations (#3093) 2024-03-05 10:16:59 +01:00
Thomas Kaul
144d831954
Release 2.61.0 (#3097) 2024-03-04 20:17:05 +01:00
Thomas Kaul
c37ad9bad4
Bugfix/fix activities import (#3095)
* Fix query parameter handling of booleans

* Update changelog
2024-03-04 20:15:41 +01:00
Thomas Kaul
4ab3f81384
Extract getFactor() (#3089)
* Extract getFactor()

* Refactoring
2024-03-03 20:04:49 +01:00
Thomas Kaul
b932bac9aa
Feature/optimize summary calculation (#3088)
* Optimize calculation

* Update changelog
2024-03-03 08:24:51 +01:00
Thomas Kaul
bcdd873222
Add missing title (#3087) 2024-03-02 17:32:32 +01:00
Thomas Kaul
25b3de5828
Release 2.60.0 (#3086) 2024-03-02 14:44:00 +01:00
Thomas Kaul
40b454d2f3
Feature/refresh cryptocurrencies list 20240302 (#3085)
* Update cryptocurrencies.json

* Add UNI7083

* Update changelog
2024-03-02 14:42:40 +01:00
Thomas Kaul
5596e5f03b
Feature/integrate wealth items into transaction point concept (#3084)
* Integrate (wealth) items into transaction point concept

* Update changelog
2024-03-02 14:29:03 +01:00
Thomas Kaul
66992ef915
Bugfix/change show condition of button to fetch current market price (#3079)
* Change show condition of button to fetch current market price

* Update changelog
2024-03-02 12:59:54 +01:00
Thomas Kaul
7f67430685
Bugfix/readd value in base currency to activity (#3078)
* Readd valueInBaseCurrency

* Update changelog
2024-03-02 10:03:10 +01:00
Thomas Kaul
8a49a04324
Feature/improve usability of benchmarks in markets overview (#3077)
* Improve icons, localize label

* Update changelog
2024-03-02 09:48:53 +01:00
Thomas Kaul
5d7c19b0ed
Fix typo (#3076) 2024-03-02 09:42:41 +01:00
Thomas Kaul
cde74b6c62
Release 2.59.0 (#3069) 2024-02-29 21:06:18 +01:00
Thomas Kaul
633c65e33c
Feature/extend self hosting faq (#3068)
* Extend self-hosting FAQ

* Update changelog
2024-02-29 21:04:47 +01:00
Thomas Kaul
d1617f2d87
Feature/add index for is excluded to account database table (#3067)
* Add index for isExcluded to account database table

* Update changelog
2024-02-29 20:50:44 +01:00
Søren Bjergmark
68e558f198
Feature/Improve activities import by ISIN number (#3051)
* Improve activities import by ISIN number

* Update changelog
2024-02-29 20:45:40 +01:00
Thomas Kaul
12ca01c862
Update OSS friends (#3066) 2024-02-29 20:26:13 +01:00
Thomas Kaul
2115745471
Bugfix/fix issue with exchange rate calculation of wealth items in accounts (#3065)
* Fix exchange rate calculatio of wealth items in accounts

* Update changelog
2024-02-29 20:14:52 +01:00
Thomas Kaul
2cabd21315
Release 2.58.0 (#3061) 2024-02-27 20:59:44 +01:00
Thomas Kaul
3615e2f057
Feature/improve handling of activities without account (#3060)
* Improve handling of activities without account

* Update changelog
2024-02-27 20:58:28 +01:00
Thomas Kaul
d3679d41b3
Bugfix/fix query to filter activities of excluded accounts (#3059)
* Fix query to filter activities of excluded accounts

* Update changelog
2024-02-27 20:58:04 +01:00
Thomas Kaul
f2d431a6b8
Bugfix/improve asset profile validation in activities import (#3057)
* Improve asset profile validation

* Update changelog
2024-02-27 20:42:23 +01:00
Thomas Kaul
2bc8bebfb8
Clean up dist folders (#3053) 2024-02-26 20:17:18 +01:00
Thomas Kaul
5b20ba3382
Release 2.57.0 (#3054) 2024-02-25 19:14:28 +01:00
Thomas Kaul
15cc294581
Feature/move break down of performance from experimental to general availability (#3047)
* Move break down of performance to general availability

* Update changelog
2024-02-25 19:12:30 +01:00
Søren Bjergmark
b060b81204
Fix debugging with VS Code due to missing Source Map (#3050)
Fixes #2801
2024-02-25 19:10:17 +01:00
Søren Bjergmark
a8d557eb1b
Disable parallel execution of commands causing race condition between mkdir and cp (#3052) 2024-02-25 19:03:28 +01:00
Thomas Kaul
6ae3a47b54
Bugfix/change top and bottom performers to performance with currency effect (#3046)
* Change to performance with currency effect

* Update changelog
2024-02-25 13:43:49 +01:00
Thomas Kaul
88c19eb45e
Feature/restructure copy assets nx target (#3045)
* Restructure copy-assets Nx target

* Update changelog
2024-02-25 11:45:00 +01:00
Thomas Kaul
7728706bc8
Release 2.56.0 (#3043) 2024-02-24 20:00:03 +01:00
Thomas Kaul
2e9d40c201
Feature/switch to performance calculations with currency effects (#3039)
* Switch to performance calculations with currency effects

* Improve value redaction in portfolio details endpoint

* Update changelog
2024-02-24 19:58:13 +01:00
Thomas Kaul
c002e37285
Feature/add missing default currency to prepare currencies function (#3042)
* Add missing default currency

* Update changelog
2024-02-24 19:45:51 +01:00
Thomas Kaul
6be38a1c19
Feature/remove is default flag from account database schema (#3041)
* Remove isDefault flag from Account database schema

* Update changelog
2024-02-24 19:44:56 +01:00
miles
a3178fb213
Feature/expose redis database via environment variable (#3036)
* Expose Redis database via environment variable

* Update changelog
2024-02-24 11:56:12 +01:00
Thomas Kaul
e7158f6e16
Feature/upgrade prisma to version 5.10.2 (#3038)
* Upgrade prisma to version 5.10.2

* Update changelog
2024-02-24 11:09:13 +01:00
Thomas Kaul
dbea0456bc
Update changelog (#3035) 2024-02-23 19:58:33 +01:00
Thomas Kaul
fefee11301
Release 2.55.0 (#3034) 2024-02-22 20:26:39 +01:00
Thomas Kaul
40836b745b
Feature/improve validation for currency in endpoints (#3030)
* Improve validation for currency

* Update changelog
2024-02-22 20:25:22 +01:00
Thomas Kaul
07eabac059
Feature/add missing database indexes part 2 (#3033)
* Add missing database indexes (for orderBy and where clauses)

* Update changelog
2024-02-22 20:21:50 +01:00
Thomas Kaul
48b412cfb8
Feature/harmonize setting of default locale (#3032)
* Harmonize setting of default locale

* Update changelog
2024-02-22 20:10:27 +01:00
Thomas Kaul
b62488628c
Prettify markup (#3029) 2024-02-21 09:58:15 +01:00
Thomas Kaul
982c71c728
Feature/set angular parser in prettierrc (#3028)
* Set parser to angular

* Update changelog
2024-02-20 19:54:03 +01:00
Thomas Kaul
5aa16a3779
Release 2.54.0 (#3027) 2024-02-19 19:47:37 +01:00
Thomas Kaul
93de25e5b6
Feature/add missing database indexes (#3026)
* Add missing database indexes

* Update changelog
2024-02-19 19:45:52 +01:00
Thomas Kaul
9acdb41aa2
Refactor params to object (#2987) 2024-02-19 19:32:10 +01:00
Thomas Kaul
ffbdfb86ec
Release 2.53.1 (#3025) 2024-02-18 18:56:34 +01:00
Thomas Kaul
be7f6bb657
Feature/add inactive as user role (#3024)
* Add INACTIVE as user role

* Update changelog
2024-02-18 18:54:49 +01:00
Thomas Kaul
6f7cbc93b9
Add missing type (#3023) 2024-02-18 18:54:36 +01:00
Thomas Kaul
0b5c71130d
Update OSS friends (#3008) 2024-02-18 18:50:04 +01:00
Thomas Kaul
0578c645d1
Release 2.53.0 (#3021) 2024-02-18 14:39:10 +01:00
Thomas Kaul
67ae86763e
Handle premium data provider in getQuotes() (#3020)
* Handle premium data provider in getQuotes()
2024-02-18 14:37:42 +01:00
Thomas Kaul
266c0a9a2c
Feature/eliminate search request in get quotes of eod service (#3019)
* Eliminate search request to get quotes

* Update changelog
2024-02-18 14:14:25 +01:00
Thomas Kaul
a3cdb23776
Feature/refactor query to filter activities of excluded accounts (#3016)
* Refactor query to filter activities of excluded accounts

* Update changelog
2024-02-18 12:29:00 +01:00
Thomas Kaul
e1371a8d2b
Clean up (#3018) 2024-02-18 12:27:34 +01:00
Thomas Kaul
448cea0b69
Feature/improve usability of holdings table (#3017)
* Improve usability

* Update changelog
2024-02-18 10:18:53 +01:00
Thomas Kaul
ad42c0bf28
Enable FAQ link for self-hoster (#3015) 2024-02-18 08:53:01 +01:00
Thomas Kaul
f50670c7fe
Feature/improve language localization for de 20240217 (#3014)
* Update translations

* Update changelog
2024-02-18 08:51:36 +01:00
Thomas Kaul
c0029d3b1d
Feature/upgrade ng extract i18n merge to version 2.10.0 (#3013)
* Upgrade ng-extract-i18n-merge to version 2.10.0

* Update changelog
2024-02-17 22:15:50 +01:00
Thomas Kaul
2518a8fd9d
Feature/add accounts tab to position detail dialog (#3012)
* Add accounts tab to position detail dialog

* Update changelog
2024-02-17 21:32:56 +01:00
Thomas Kaul
572dcf075a
Release 2.52.0 (#3011) 2024-02-16 20:05:01 +01:00
Thomas Kaul
29cb83d469
Bugfix/improve x axis scale of dividend and investment timeline (#3010)
* Improve X-axis scale

* Update changelog
2024-02-16 20:03:20 +01:00
Thomas Kaul
cac73ac111
Feature/divide faq page in three sections (#3003)
* Divide FAQ page in three sections

* General
* Cloud (SaaS)
* Self-Hosting

* Update changelog
2024-02-16 18:57:34 +01:00
Thomas Kaul
02cf4295a9
Feature/add loading indicator to dividend and investment timelines (#3007)
* Add loading indicators

* Dividend timeline
* Investment timeline

* Update changelog
2024-02-16 09:43:51 +01:00
Thomas Kaul
78b3328bf7
Add activities count (#3005) 2024-02-15 10:25:47 +01:00
Thomas Kaul
e0d6d9e8ca
Migrate if / else to control flow (#3001) 2024-02-13 20:46:21 +01:00
Thomas Kaul
54310f2214
Feature/add support for jupiter cryptocurrency (#2999)
* Add JUP29210

* Update changelog
2024-02-13 19:46:15 +01:00
Thomas Kaul
1fec49fbc2
Improve states (#3000) 2024-02-13 19:44:17 +01:00
Thomas Kaul
d00489b547
Release 2.51.0 (#2998) 2024-02-12 20:37:07 +01:00
Thomas Kaul
2985dd67c5
Feature/improve ordered list in safari (#2996)
* Improve ordered list in Safari (without text-truncate)

* Update changelog
2024-02-12 20:35:16 +01:00
Thomas Kaul
5eba764c04
Feature/upgrade eslint dependencies 20240212 (#2995)
* Upgrade eslint dependencies

* Update changelog
2024-02-12 17:22:29 +01:00
Thomas Kaul
cc0ce18627
Bugfix/fix date conversion in import of historical market data (#2994)
* Fix date conversion

* Update changelog
2024-02-12 17:21:22 +01:00
Thomas Kaul
b758654158
Fix renew plan label (#2993) 2024-02-11 21:37:11 +01:00
Thomas Kaul
d5d40c0ea1
Migrate to control flow (#2991)
* Migrate to control flow
2024-02-11 20:51:06 +01:00
Thomas Kaul
fd294d4d2b
Migrate to control flow (#2992) 2024-02-11 20:47:24 +01:00
Thomas Kaul
e82cf2e7d0
Feature/upgrade nx to version 18.0.4 (#2990)
* Upgrade Nx to version 18.0.4

* Update changelog
2024-02-11 20:19:45 +01:00
Thomas Kaul
446c7cb517
Remove closing tags of mat-* components (#2989) 2024-02-11 18:12:16 +01:00
Thomas Kaul
e921ed7f52
Reorder imports (#2988) 2024-02-11 17:50:18 +01:00
Thomas Kaul
865402be3a
Feature/replace import sort with prettier plugin sort imports (#2872)
* Replace import-sort with prettier-plugin-sort-imports

* Update changelog
2024-02-11 17:26:35 +01:00
Thomas Kaul
6eb659d7e6
Release 2.50.0 (#2984) 2024-02-11 12:41:58 +01:00
Thomas Kaul
37430b7bdc
Feature/upgrade prisma to version 5.9.1 (#2983)
* Upgrade prisma to version 5.9.1

* Update changelog
2024-02-11 12:38:49 +01:00
Thomas Kaul
ef9d77312e
Introduce renewal-early-bird (#2982) 2024-02-11 12:33:34 +01:00
Thomas Kaul
ccaf06360a
Feature/introduce admin setting to disable data gathering (#2981)
* Introduce setting to disable data gathering

* Update changelog
2024-02-11 10:06:07 +01:00
Thomas Kaul
f83e75df44
Feature/harmonize env variables of various api keys (#2980)
* Harmonize env variables of various API keys

* Update changelog
2024-02-11 09:44:12 +01:00
Thomas Kaul
00a2b60eb5
Release 2.49.0 (#2979) 2024-02-09 19:30:00 +01:00
Thomas Kaul
fcbf2f1645
Feature/remove lazy from name of activities table (#2978)
* Remove lazy from name

* Update translations
2024-02-09 18:48:05 +01:00
Thomas Kaul
460266a501
Feature/upgrade yahoo finance2 to version 2.9.1 (#2963)
* Upgrade yahoo-finance2 to version 2.9.1

* Update changelog
2024-02-09 18:41:18 +01:00
Thomas Kaul
9fe90273c7
Feature/move assistant to general availability (#2977)
* Move assistant from experimental to general availability

* Update changelog
2024-02-09 18:25:15 +01:00
Thomas Kaul
4078229fe6
Feature/add button to apply filters in assistant (#2971)
* Add apply filters button

* Update changelog
2024-02-09 09:45:54 +01:00
Thomas Kaul
609c03f174
Add analytics image (#2970) 2024-02-08 08:00:04 +01:00
Martin Mui
e7d4641d13
Feature/reload data on logo click (#2959)
* Reload data on logo click

* Update changelog
2024-02-07 21:03:28 +01:00
Thomas Kaul
cc1d9811e0
Release 2.48.1 (#2968) 2024-02-06 17:00:40 +01:00
Thomas Kaul
35450ac004
Bugfix/add missing data provider info to search results of coingecko (#2967)
* Add missing data provider info

* Update changelog
2024-02-06 16:58:54 +01:00
Thomas Kaul
9c18f48a32
Release 2.48.0 (#2962) 2024-02-05 19:57:40 +01:00
Thomas Kaul
87529490c3
Feature/refresh cryptocurrencies list 20240205 (#2961)
* Update cryptocurrencies.json

* Update changelog
2024-02-05 19:56:16 +01:00
Thomas Kaul
893e76f83f
Feature/provide data provider info in search (#2958)
* Provide data provider info in search

* Update changelog
2024-02-05 19:55:39 +01:00
Thomas Kaul
06ba7a4b1b
Feature/extend assistant by asset class selector (#2957)
* Remove tabs

* Add asset class selector

* Update changelog
2024-02-04 15:50:58 +01:00
Thomas Kaul
c68d113d27
Feature/improve usability of account and tag selector of assistant (#2955)
* Change radio button to select

* account
* tag

* Update changelog
2024-02-04 12:11:01 +01:00
Thomas Kaul
69e3bee52c
Feature/upgrade prettier to version 3.2.5 (#2954)
* Upgrade prettier to version 3.2.5

* Update changelog
2024-02-04 12:10:33 +01:00
Thomas Kaul
cea569c987
Add Fina (#2956) 2024-02-04 12:10:22 +01:00
Hey
2a38a16f6b
Feature/Improve error logs for timeout in data provider services (#2953)
* Improve error logs for timeout in data provider services

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-02-04 11:56:00 +01:00
Thomas Kaul
0f9455cf02
Release 2.47.0 (#2951) 2024-02-03 09:44:29 +01:00
Thomas Kaul
d4afa03505
Bugfix/fix rendering issue with date range selector of assistant (#2950)
* Improve click handling

* Improve locales

* Update changelog
2024-02-03 09:42:50 +01:00
Thomas Kaul
c9237146e2
Feature/add investment value to chart (#2948)
* Add investment value to chart

* Update changelog
2024-02-03 09:23:19 +01:00
Thomas Kaul
faad65b6f3
Update translations (#2940)
* Update translations

* Update changelog
2024-02-02 20:42:12 +01:00
Thomas Kaul
e459c72100
Update OSS friends (#2942) 2024-02-01 20:54:25 +01:00
Thomas Kaul
a8add30125
Feature/upgrade prettier to version 3.2.4 (#2928)
* Upgrade prettier to version 3.2.4

* Update changelog
2024-01-31 18:12:09 +01:00
Thomas Kaul
b535aee91d
Remove reference to Internet Identity (#2946) 2024-01-31 17:19:05 +01:00
Thomas Kaul
4434d0315f
Remove unused timeline calculation (#2947) 2024-01-30 20:56:41 +01:00
Thomas Kaul
8b10695353
Feature/only show used tags in tag selector of assistant (#2943)
* Only show used tags in tag selector

* Update changelog
2024-01-29 19:53:47 +01:00
Arshad Jamal
e82dcc8ace
Feature/fix export in lazy-loaded activities table (#2939)
* Fix export in lazy-loaded activities table

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-01-29 19:37:09 +01:00
Thomas Kaul
6dcb0d8583
Release 2.46.0 (#2938) 2024-01-28 10:00:07 +01:00
Thomas Kaul
40b6777814
Add upgrade plan button (#2937) 2024-01-28 09:58:38 +01:00
Thomas Kaul
25deba16df
Feature/add reset filters button to assistant (#2936)
* Add reset filters button

* Update changelog
2024-01-28 09:47:28 +01:00
Thomas Kaul
be93ca8968
Feature/migrate allocations page to work with filters of assistant (#2933)
* Migrate portfolio allocations to work with filters of assistant

* Update changelog
2024-01-28 09:20:32 +01:00
Thomas Kaul
0436cc6487
Migrate ngx-skeleton-loader components to self-closing tags (#2935) 2024-01-28 08:51:02 +01:00
Thomas Kaul
857708dc4d
Migrate gf-* components to self-closing tags (#2934) 2024-01-28 08:50:43 +01:00
Thomas Kaul
1ca4f885b0
Feature/migrate holdings page to work with filters of assistant (#2932)
* Migrate portfolio holdings to work with filters of assistant

* Update changelog
2024-01-27 19:28:13 +01:00
Thomas Kaul
c9368c5cf2
Release 2.45.0 (#2931) 2024-01-27 10:54:35 +01:00
Thomas Kaul
29423efea3
Update translations (#2930) 2024-01-27 10:53:19 +01:00
Thomas Kaul
f3ee99fb2b
Feature/extend assistant by account selector (#2929)
* Add account selector to assistant

* Update changelog
2024-01-27 10:48:46 +01:00
Francisco Silva
3df8810412
Feature/Add support to grant private access with permissions (#2870)
* Add support to grant private access with permissions

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-01-27 09:44:13 +01:00
Thomas Kaul
b8ca88c6df
Add auto-renewal (#2920) 2024-01-27 08:46:27 +01:00
Thomas Kaul
2c068c412d
Feature/migrate tag selector to form group in assistant (#2926)
* Introduce filter form group

* Update changelog
2024-01-27 08:45:55 +01:00
Thomas Kaul
9fdbd22cb5
Bugfix/fix activities import for manual data source (#2923)
* Fix import

* Update changelog
2024-01-27 08:41:45 +01:00
Thomas Kaul
8f5f4c5875
Feature/format name in eod historical data service (#2922)
* Format name

* Update changelog
2024-01-26 22:37:47 +01:00
Thomas Kaul
50fb82a6e6
Extend personal finance tools (#2925) 2024-01-26 22:37:26 +01:00
Thomas Kaul
2c10cd7edf
Bugfix/remove holdings with incomplete data from top3 bottom3 performers (#2921)
* Remove holdings with incomplete data

* Update changelog
2024-01-26 08:35:23 +01:00
Thomas Kaul
bbde86c66e
Feature/improve language localization for de 20240124 (#2918)
* Update translations

* Update changelog
2024-01-25 21:11:07 +01:00
Thomas Kaul
73c0843d51
Feature/add permissions to access model (#2833)
* Add permissions to Access model

* Update changelog
2024-01-24 19:23:58 +01:00
Thomas Kaul
04fc2cd3e1
Release 2.44.0 (#2917) 2024-01-24 12:26:36 +01:00
Thomas Kaul
b39c97ab9f
Bugfix/improve validation for non numeric results in eod service (#2916)
* Improve validation of non-numeric numbers

* Update changelog
2024-01-24 12:24:38 +01:00
Thomas Kaul
1dd5e9c787
Release 2.43.1 (#2914) 2024-01-23 20:46:37 +01:00
Daniel Bodky
a9985b65b8
Adjust Dockerfile to enable healthcheck (#2913) 2024-01-23 20:44:57 +01:00
Thomas Kaul
0a35d5f236
Release 2.43.0 (#2911) 2024-01-23 15:49:58 +01:00
Thomas Kaul
09ce8b1cd0
Feature/add support for importing dividends from eod (#2910)
* Add support for importing dividends

* Update changelog
2024-01-23 15:48:09 +01:00
Cédric Meuter
a5ed49fe4c
Feature/Add date range selector to assistant (#2905)
* Add date range selector including WTD and MTD to assistant

* Update changelog
2024-01-23 11:57:37 +01:00
Thomas Kaul
5c23ece62c
Feature/improve usability of benchmark management (#2904)
* Add icon

* Update changelog
2024-01-22 20:22:32 +01:00
Cédric Meuter
4e9e3f7b6b
Feature/Add wtd and mtd as possible values for date range (#2902)
* Add `wtd` and `mtd` as possible values for date range
  'wtd': week-to-date (from the start of the week)
  'mtd': month-to-date (from the start of the month)

* Update changelog
2024-01-21 16:51:30 +01:00
Thomas Dietrich
5fc84a06cc
Feature/Add healthcheck for Ghostfolio service (#2893)
* Add curl to Dockerfile image

* Add healthcheck to docker-compose.yml and docker-compose.build.yml

* Update changelog
2024-01-21 11:48:32 +01:00
Thomas Kaul
12186e1c6c
Release 2.42.0 (#2899) 2024-01-21 11:16:04 +01:00
Thomas Kaul
f2803aecbc
Add guard (#2898) 2024-01-21 11:14:44 +01:00
Thomas Kaul
5ba5b86d5f
Feature/improve handling of derived currencies (#2891)
* Improve handling of derived currencies

* Update changelog
2024-01-21 11:12:48 +01:00
Thomas Kaul
6167f105fe
Refactoring (#2897) 2024-01-21 10:27:10 +01:00
Thomas Kaul
8d5f2fd91d
Fix fee conversion (#2896)
* Fix fee conversion

* Update changelog
2024-01-21 10:24:43 +01:00
Thomas Kaul
4ac661fb94
Feature/improve language localization for de 20240120 (#2894)
* Improve translations

* Update changelog
2024-01-21 10:16:18 +01:00
Thomas Kaul
e763bfb2e2
Feature/improve labels in portfolio evolution chart and investment timeline (#2892)
* Improve labels

* Update changelog
2024-01-20 09:15:05 +01:00
Thomas Kaul
88c7e34cc3
Feature/upgrade prisma to version 5.8.1 (#2859)
* Upgrade prisma to version 5.8.1

* Update changelog
2024-01-18 19:22:20 +01:00
Thomas Kaul
0ee632470e
Improve typings (#2889) 2024-01-17 18:06:53 +01:00
Hugo Persson
c918deeb1c
Feature/Support for editing countries and sectors (#2854)
* Add support for editing countries and sectors

* Update changelog
2024-01-17 11:40:02 +01:00
Thomas Kaul
1877b31f00
Release 2.41.0 (#2888) 2024-01-16 22:33:13 +01:00
Thomas Kaul
00895b7bb1
Feature/increase timeout to load historical data in data provider service (#2887)
* Increase timeout to load historical data

* Update changelog
2024-01-16 22:31:28 +01:00
Thomas Kaul
bff60ddbe0
Feature/improve asset profile validation in activities import for manual data source (#2886)
* Improve asset profile validation for MANUAL data source

* Update changelog
2024-01-16 21:21:51 +01:00
Thomas Kaul
d46de0a15e
Fix typo (#2878) 2024-01-16 19:42:56 +01:00
Thomas Kaul
7b45a8b3fc
Introduce type (#2885) 2024-01-16 19:42:39 +01:00
Thomas Kaul
693791d113
Feature/add validation of search results in eod historical data service (#2883)
* Validate currency

* Update changelog
2024-01-16 18:03:31 +01:00
TobiasXy
1b2d2a9860
Feature/Add holdings tab to account detail dialog (#2853) (#2864)
* Feature/Add holdings tab to account detail dialog (#2853)

* Update changelog
2024-01-15 20:35:45 +01:00
Thomas Kaul
bde8be1385
Release 2.40.0 (#2877) 2024-01-15 19:42:11 +01:00
Thomas Kaul
74ca058364
Extend pricing page section (#2876) 2024-01-15 19:40:27 +01:00
Thomas Kaul
ba3cf82c6e
Feature/increase robustness of exchange rates by always getting quotes (#2875)
* Always get quotes (with fallback to historical data)

* Update changelog
2024-01-15 19:02:00 +01:00
Thomas Kaul
217bb6aa5a
Release 2.39.0 (#2871) 2024-01-14 17:25:05 +01:00
Thomas Kaul
440dc470fa
Bugfix/fix currency inconsistency with conversion of ZAR to ZAc (#2869)
* Fix conversion from ZAR to ZAc

* Update changelog
2024-01-14 17:22:03 +01:00
Thomas Kaul
165ca94f5b
Feature/improve alignment in portfolio performance component (#2867)
* Improve alignment

* Update changelog
2024-01-14 17:05:42 +01:00
Thomas Kaul
c418e75139
Bugfix/fix currency in error log in exchange rate data service (#2868)
* Fix currency

* Update changelog
2024-01-14 10:36:32 +01:00
Thomas Kaul
76bf839010
Release 2.38.0 (#2865) 2024-01-13 16:19:57 +01:00
Thomas Kaul
3bdc4c9b4a
Feature/upgrade prettier to version 3.2.1 (#2860)
* Upgrade prettier to version 3.2.1

* Update changelog
2024-01-13 16:18:18 +01:00
Thomas Kaul
005890d785
Improve extractNumberFromString() for international number formats (#2843)
* Set up test

* Add support for international formatted numbers

* Expose locale in scraper configuration

* Update changelog
2024-01-13 16:17:38 +01:00
Thomas Kaul
256c020e88
Feature/improve indicator for delayed market data (#2862)
* Improve indicator for delayed market data

* Update changelog
2024-01-13 16:08:37 +01:00
Thomas Kaul
5fa3388609
Feature/break down performance into asset and currency (#2863)
* Break down performance into asset and currency

* Nullify values

* Update changelog
2024-01-13 14:23:00 +01:00
gizmodus
be801b481e
Feature/Add exchange rate effects to portfolio calculation (#2834)
* Add exchange rate effects to portfolio calculation

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2024-01-13 13:07:33 +01:00
Thomas Kaul
a72e98f73c
Add AllInvestView (#2861) 2024-01-13 13:06:18 +01:00
Thomas Kaul
f5df970685
Release 2.37.0 (#2858) 2024-01-11 18:57:59 +01:00
Thomas Kaul
edfdc0c346
Feature/improve chart size in asset profile details dialog (#2849)
* Improve chart size

* Update changelog
2024-01-11 18:55:52 +01:00
Thomas Kaul
fcfe7b1787
Clean up (#2844) 2024-01-11 18:55:34 +01:00
Hugo Persson
170b8acc65
Feature/Add git pre-commit hook for yarn format (#2840)
* Set up git pre-commit hook for yarn format

* Update changelog
2024-01-10 20:36:10 +01:00
Thomas Kaul
a47829082e
Bugfix/fix hidden fifth tab on mobile (#2848)
* Fix hidden fifth tab

* Update changelog
2024-01-09 08:28:01 +01:00
Thomas Kaul
48ab5fcf08
Feature/update docker compose instructions to compose v2 (#2836)
* Update docker compose instructions

* Update changelog
2024-01-08 20:21:47 +01:00
Thomas Kaul
dc8b60eeb1
Rename "Jobs" to "Job Queue" (#2847) 2024-01-08 20:21:13 +01:00
Thomas Kaul
ee67432ffc
Release 2.36.0 (#2842) 2024-01-07 17:15:30 +01:00
Thomas Kaul
7755a6b655
Update translations (#2841) 2024-01-07 17:13:19 +01:00
Thomas Kaul
d7f72819de
Feature/extend assistant by tag selector (#2838)
* Extend assistant by tag selector

* Update changelog
2024-01-07 16:56:25 +01:00
Thomas Kaul
2a4d7bf14f
Feature/improve language localization for de 20240106 (#2837)
* Update translations

* Update changelog
2024-01-07 16:52:02 +01:00
Thomas Kaul
d49287922f
Feature/refresh cryptocurrencies list 20240106 (#2835)
* Update cryptocurrencies.json

* Update changelog
2024-01-06 19:29:03 +01:00
Thomas Kaul
ac0f6f40cf
Remove closing tags (#2816) 2024-01-06 19:28:35 +01:00
Tanguy Herbron
d91f947ab0
Feature/Add Coingecko api keys support (#2827)
* Add CoinGecko API keys support

* Update changelog
2024-01-06 19:06:07 +01:00
Thomas Kaul
af71274ea9
Feature/remove account type enum (#2832)
* Remove AccountType enum

* Update changelog
2024-01-06 14:19:42 +01:00
Thomas Kaul
0feba4b8d9
Release 2.35.0 (#2831) 2024-01-06 10:45:26 +01:00
Francisco Silva
62f85293e2
#2820 Grant private access (#2822)
* Grant private access

* Update changelog
2024-01-06 10:27:21 +01:00
Thomas Kaul
6a048cee85
Feature/add hint for twr to portfolio summary (#2824)
* Add hint for TWR

* Add TWR to README.md

* Update changelog
2024-01-06 09:31:59 +01:00
Thomas Kaul
0d93612d16
Feature/improve style of assistant (#2828)
* Minor style improvements

* Update changelog
2024-01-06 09:14:48 +01:00
psychowood
9bf68b0d20
Feature/enable redis authentication in docker compose files (#2805)
* Enable redis authentication in docker-compose files

* Update changelog
2024-01-04 20:14:45 +01:00
Hugo Persson
371f1dc451
Feature/support rest api in scraper (#2810)
* Support REST APIs in scraper

* Update changelog
2024-01-03 21:59:45 +01:00
Thomas Kaul
5cb2ec6411
Feature/Improve user interface of access table (#2821)
* Improve alignment

* Update changelog
2024-01-03 21:08:35 +01:00
Thomas Kaul
3723a1d8b8
Release 2.34.0 (#2817) 2024-01-02 17:05:12 +01:00
Thomas Kaul
4c30e9459d
Feature/extend assistant by date range selector (#2815)
* Extend assistant by date range selector

* Update changelog
2024-01-02 17:02:15 +01:00
Thomas Kaul
23d323073d
Fix performance percentage for 1d (#2814)
* Fix performance percentage for 1d

* Improve response of positions endpoint

* Update changelog
2024-01-02 14:10:08 +01:00
Thomas Kaul
0ad734262a
Bugfix/improve tabs on ios (#2811)
* Improve tabs on iOS (Add to Home Screen)

* Update changelog
2024-01-02 10:06:13 +01:00
Thomas Kaul
0649f9fd2c
Clean up (#2787) 2024-01-02 10:05:31 +01:00
Thomas Kaul
d089662dab
Feature/improve the style of the top 3 and bottom 3 performers (#2807)
* Refactor to ordered list

* Update changelog
2024-01-02 09:44:15 +01:00
Thomas Kaul
8c1c336fc6
Feature/upgrade nx to version 17.2.8 (#2809)
* Upgrade Nx to version 17.2.8

* Update changelog
2024-01-01 17:14:53 +01:00
Thomas Kaul
43b4f14ace
Feature/add button to test scraper configuration (#2808)
* Add button to test scraper configuration

* Update changelog

---------

Co-authored-by: Manushreshta B L <manushreshta27@gmail.com>
Co-authored-by: Hugo Persson <hugo.e.persson@gmail.com>
2024-01-01 11:53:42 +01:00
Thomas Kaul
3717e38845
Update year (#2803) 2024-01-01 10:11:04 +01:00
Thomas Kaul
265d4d0450
Release 2.33.0 (#2806) 2023-12-31 13:30:27 +01:00
gizmodus
726e727c7d
Feature/benchmark currency correction (#2790)
* Convert benchmark performance to base currency

* Introduce getExchangeRates() for multiple dates

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
2023-12-31 13:28:11 +01:00
Thomas Kaul
cb664774c0
Update translations (#2798)
* Update translations
2023-12-31 10:33:12 +01:00
Thomas Kaul
b89bf1d5e8
Feature/increase timeout to load currencies (#2800)
* Increase timeout

* Update changelog
2023-12-31 10:23:56 +01:00
Thomas Kaul
53ce37a83a
Update OSS friends (#2799) 2023-12-31 10:23:13 +01:00
Thomas Kaul
e9ac9057ff
Fix debug instructions (#2802) 2023-12-30 21:11:56 +01:00
Thomas Kaul
7020fc2a93
Feature/add hint for community language support (#2793)
* Add hint

* Update changelog
2023-12-30 10:55:11 +01:00
Thomas Kaul
efcd9539dd
Feature/upgrade ng extract i18n merge to 2.9.1 (#2797)
* Upgrade ng-extract-i18n-merge to version 2.9.1

* Update changelog
2023-12-30 10:54:04 +01:00
Thomas Kaul
61ecc48d0e
Feature/improve language localization for de 20231229 (#2796)
* Improve language localizations

* Update changelog
2023-12-30 10:34:17 +01:00
Thomas Kaul
e465f1b791
Feature/add support to edit currency of asset profile with data source manual (#2789)
* Add support to edit currency

* Update changelog
2023-12-29 19:55:51 +01:00
Thomas Kaul
01b6c14bcc
Feature/improve handling of derived currency usx (#2788)
* Improve handling of USX

* Update changelog
2023-12-29 17:31:50 +01:00
miles
34b02210df
Feature/expose the environment variable REQUEST_TIMEOUT (#2792)
* Expose the environment variable `REQUEST_TIMEOUT`

* Update changelog
2023-12-29 17:29:33 +01:00
Thomas Kaul
0034776b34
Feature/upgrade nx to version 17.2.7 (#2781)
* Upgrade Nx to version 17.2.7

* Update changelog
2023-12-29 11:26:24 +01:00
gizmodus
b183c45027
Time weighted portfolio performance calculation (#2778)
* Implement time weighted portfolio performance calculation

* Update changelog
2023-12-27 15:55:35 +01:00
underwater
7d68905f1b
Feature/use has permission annotation in endpoints (#2771)
* Use HasPermission in endpoints

* Update changelog
2023-12-26 19:23:25 +01:00
Thomas Kaul
0953c072fe
Release 2.32.0 (#2784) 2023-12-26 10:18:32 +01:00
Thomas Kaul
d152187ee8
Feature/upgrade prisma to version 5.7.1 (#2780)
* Upgrade prisma to version 5.7.1

* Update changelog
2023-12-26 10:16:20 +01:00
Thomas Kaul
3c5affce88
Feature/upgrade prettier to version 3.1.1 (#2768)
* Upgrade prettier to version 3.1.1

* Update changelog
2023-12-24 16:23:17 +01:00
Thomas Kaul
f27e21f9a0
Extend issue template (#2776) 2023-12-23 17:45:37 +01:00
Thomas Kaul
337ca328c3
Feature/drop activity id on import (#2769)
* Drop activity id on import

* Update changelog
2023-12-22 20:16:02 +01:00
Thomas Kaul
beb9e2c43f
Feature/modernize nx executors (#2767)
* Modernize Nx executors

* @nx/eslint:lint
* @nx/webpack:webpack

* Update changelog
2023-12-21 11:44:36 +01:00
Thomas Kaul
4d79df90a7
Feature/support search by asset profile id (#2765)
* Add support to search for an asset profile by id

* Update changelog
2023-12-20 19:24:03 +01:00
Thomas Kaul
aa72d9b730
Feature/improve validation of currency management (#2761)
* Improve validation

* Update changelog
2023-12-20 11:53:40 +01:00
1066 changed files with 133837 additions and 127750 deletions

25
.env.dev Normal file
View File

@ -0,0 +1,25 @@
COMPOSE_PROJECT_NAME=ghostfolio-development
# CACHE
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=<INSERT_REDIS_PASSWORD>
# POSTGRES
POSTGRES_DB=ghostfolio-db
POSTGRES_USER=user
POSTGRES_PASSWORD=<INSERT_POSTGRES_PASSWORD>
# VARIOUS
ACCESS_TOKEN_SALT=<INSERT_RANDOM_STRING>
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
JWT_SECRET_KEY=<INSERT_RANDOM_STRING>
# DEVELOPMENT
# Nx 18 enables using plugins to infer targets by default
# This is disabled for existing workspaces to maintain compatibility
# For more info, see: https://nx.dev/concepts/inferred-tasks
NX_ADD_PLUGINS=false
NX_NATIVE_COMMAND_RUNNER=false

View File

@ -1,7 +1,7 @@
COMPOSE_PROJECT_NAME=ghostfolio-development
COMPOSE_PROJECT_NAME=ghostfolio
# CACHE
REDIS_HOST=localhost
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=<INSERT_REDIS_PASSWORD>
@ -10,6 +10,7 @@ POSTGRES_DB=ghostfolio-db
POSTGRES_USER=user
POSTGRES_PASSWORD=<INSERT_POSTGRES_PASSWORD>
# VARIOUS
ACCESS_TOKEN_SALT=<INSERT_RANDOM_STRING>
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?connect_timeout=300&sslmode=prefer
JWT_SECRET_KEY=<INSERT_RANDOM_STRING>

View File

@ -1,118 +0,0 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {}
},
{
"files": ["*.ts"],
"plugins": ["eslint-plugin-import", "@typescript-eslint"],
"rules": {
"@typescript-eslint/consistent-type-definitions": "error",
"@typescript-eslint/dot-notation": "off",
"@typescript-eslint/explicit-member-accessibility": [
"off",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/naming-convention": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-inferrable-types": [
"error",
{
"ignoreParameters": true
}
],
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-shadow": [
"error",
{
"hoist": "all"
}
],
"@typescript-eslint/no-unused-expressions": "error",
"@typescript-eslint/prefer-function-type": "error",
"@typescript-eslint/unified-signatures": "error",
"arrow-body-style": "off",
"constructor-super": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"id-blacklist": "off",
"id-match": "off",
"import/no-deprecated": "warn",
"no-bitwise": "error",
"no-caller": "error",
"no-console": [
"error",
{
"allow": [
"log",
"warn",
"dir",
"timeLog",
"assert",
"clear",
"count",
"countReset",
"group",
"groupEnd",
"table",
"dirxml",
"error",
"groupCollapsed",
"Console",
"profile",
"profileEnd",
"timeStamp",
"context"
]
}
],
"no-debugger": "error",
"no-empty": "off",
"no-eval": "error",
"no-fallthrough": "error",
"no-new-wrappers": "error",
"no-restricted-imports": ["error", "rxjs/Rx"],
"no-throw-literal": "error",
"no-undef-init": "error",
"no-underscore-dangle": "off",
"no-var": "error",
"prefer-const": "error",
"radix": "error"
}
}
],
"extends": [null, "plugin:storybook/recommended"]
}

View File

@ -6,7 +6,13 @@ labels: ''
assignees: ''
---
The Issue tracker is **ONLY** used for reporting bugs. New features should be discussed in our [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) community or in [Discussions](https://github.com/ghostfolio/ghostfolio/discussions).
**Important Notice**
The issue tracker is **ONLY** used for reporting bugs. New features should be discussed in our [Slack](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) community or in [Discussions](https://github.com/ghostfolio/ghostfolio/discussions).
Incomplete or non-reproducible issues may be closed, but we are here to help! If you encounter difficulties reproducing the bug or need assistance, please reach out to our community channels mentioned above.
Thank you for your understanding and cooperation!
**Bug Description**
@ -36,8 +42,9 @@ The Issue tracker is **ONLY** used for reporting bugs. New features should be di
<!-- Please complete the following information -->
- Cloud or Self-hosted
- Ghostfolio Version X.Y.Z
- Cloud or Self-hosted
- Experimental Features enabled or disabled
- Browser
- OS

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,3 +1,5 @@
/.nx/cache
/.nx/workspace-data
/apps/client/src/polyfills.ts
/dist
/test/import

View File

@ -9,7 +9,26 @@
],
"attributeSort": "ASC",
"endOfLine": "auto",
"plugins": ["prettier-plugin-organize-attributes"],
"importOrder": ["^@ghostfolio/(.*)$", "<THIRD_PARTY_MODULES>", "^[./]"],
"importOrderSeparation": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
},
{
"files": "*.ts",
"options": {
"importOrderParserPlugins": ["decorators-legacy", "typescript"]
}
}
],
"plugins": [
"prettier-plugin-organize-attributes",
"@trivago/prettier-plugin-sort-imports"
],
"printWidth": 80,
"singleQuote": true,
"tabWidth": 2,

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,7 @@ Remove permission in `UserService` using `without()`
### Frontend
Use `*ngIf="user?.settings?.isExperimentalFeatures"` in HTML template
Use `@if (user?.settings?.isExperimentalFeatures) {}` in HTML template
## Git
@ -30,26 +78,26 @@ 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

View File

@ -1,61 +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 \
git \
g++ \
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 \
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
COPY --from=builder /ghostfolio/dist/apps /ghostfolio/apps
RUN apt-get update && apt-get install -y --no-install-suggests \
curl \
openssl \
&& rm -rf /var/lib/apt/lists/*
COPY --chown=node:node --from=builder /ghostfolio/dist/apps /ghostfolio/apps
COPY --chown=node:node ./docker/entrypoint.sh /ghostfolio/entrypoint.sh
WORKDIR /ghostfolio/apps/api
EXPOSE ${PORT:-3333}
CMD [ "yarn", "start:production" ]
USER node
CMD [ "/ghostfolio/entrypoint.sh" ]

204
README.md
View File

@ -7,13 +7,11 @@
**Open Source Wealth Management Software**
[**Ghostfol.io**](https://ghostfol.io) | [**Live Demo**](https://ghostfol.io/en/demo) | [**Ghostfolio Premium**](https://ghostfol.io/en/pricing) | [**FAQ**](https://ghostfol.io/en/faq) |
[**Blog**](https://ghostfol.io/en/blog) | [**Slack**](https://join.slack.com/t/ghostfolio/shared_invite/zt-vsaan64h-F_I0fEo5M0P88lP9ibCxFg) | [**Twitter**](https://twitter.com/ghostfolio_)
[**Blog**](https://ghostfol.io/en/blog) | [**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 for `Today`, `YTD`, `1Y`, `5Y`, `Max`
- ✅ Portfolio performance: Time-weighted rate of return (TWR) 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,19 +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 |
| `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_HOST` | | The host where _Redis_ is running |
| `REDIS_PASSWORD` | | The password of _Redis_ |
| `REDIS_PORT` | | The port where _Redis_ is running |
| 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
@ -115,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
@ -123,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
@ -134,62 +137,27 @@ docker-compose --env-file ./.env -f docker/docker-compose.build.yml up -d
#### Upgrade Version
1. Increase the version of the `ghostfolio/ghostfolio` Docker image in `docker/docker-compose.yml`
1. Run the following command to start the new Docker image: `docker-compose --env-file ./.env -f docker/docker-compose.yml up -d`
At each start, the container will automatically apply the database schema migrations if needed.
1. Update the _Ghostfolio_ Docker image
- Increase the version of the `ghostfolio/ghostfolio` Docker image in `docker/docker-compose.yml`
- Run the following command if `ghostfolio:latest` is set:
```bash
docker compose -f docker/docker-compose.yml pull
```
1. Run the following command to start the new Docker image:
```bash
docker compose -f docker/docker-compose.yml up -d
```
The container will automatically apply any required database schema migrations during startup.
### Home Server Systems (Community)
Ghostfolio is available for various home server systems, including [Runtipi](https://www.runtipi.io/docs/apps-available), [TrueCharts](https://truecharts.org/charts/stable/ghostfolio), [Umbrel](https://apps.umbrel.com/app/ghostfolio), and [Unraid](https://unraid.net/community/apps?q=ghostfolio).
Ghostfolio is available for various home server systems, including [CasaOS](https://github.com/bigbeartechworld/big-bear-casaos), [Home Assistant](https://github.com/lildude/ha-addon-ghostfolio), [Runtipi](https://www.runtipi.io/docs/apps-available), [TrueCharts](https://truecharts.org/charts/stable/ghostfolio), [Umbrel](https://apps.umbrel.com/app/ghostfolio), and [Unraid](https://unraid.net/community/apps?q=ghostfolio).
## Development
### Prerequisites
- [Docker](https://www.docker.com/products/docker-desktop)
- [Node.js](https://nodejs.org/en/download) (version 18+)
- [Yarn](https://yarnpkg.com/en/docs/install)
- Create a local copy of this Git repository (clone)
- Copy the file `.env.example` to `.env` and populate it with your data (`cp .env.example .env`)
### Setup
1. Run `yarn install`
1. Run `docker-compose --env-file ./.env -f docker/docker-compose.dev.yml up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
1. Run `yarn database:setup` to initialize the database schema
1. 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 _Launch Program_ 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
@ -201,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`
@ -230,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
@ -262,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
@ -272,12 +296,16 @@ 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).
## Analytics
![Alt](https://repobeats.axiom.co/api/embed/281a80b2d0c4af1162866c24c803f1f18e5ed60e.svg 'Repobeats analytics image')
## License
© 2021 - 2023 [Ghostfolio](https://ghostfol.io)
© 2021 - 2025 [Ghostfolio](https://ghostfol.io)
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).

13
SECURITY.md Normal file
View File

@ -0,0 +1,13 @@
# Security Policy
## Reporting Security Issues
If you discover a security vulnerability in this repository, please report it to security[at]ghostfol.io. We will acknowledge your report and provide guidance on the next steps.
To help us resolve the issue, please include the following details:
- A description of the vulnerability
- Steps to reproduce the vulnerability
- Affected versions of the software
We appreciate your responsible disclosure and will work to address the issue promptly.

View File

@ -1,22 +0,0 @@
{
"extends": "../../.eslintrc.json",
"ignorePatterns": ["!**/*"],
"rules": {},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"parserOptions": {
"project": ["apps/api/tsconfig.*?.json"]
},
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View File

@ -0,0 +1,31 @@
const baseConfig = require('../../eslint.config.cjs');
module.exports = [
{
ignores: ['**/dist']
},
...baseConfig,
{
rules: {}
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {},
languageOptions: {
parserOptions: {
project: ['apps/api/tsconfig.*?.json']
}
}
},
{
files: ['**/*.ts', '**/*.tsx'],
// Override or add rules here
rules: {}
},
{
files: ['**/*.js', '**/*.jsx'],
// Override or add rules here
rules: {}
}
];

View File

@ -13,7 +13,6 @@ export default {
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/apps/api',
testTimeout: 10000,
testEnvironment: 'node',
preset: '../../jest.preset.js'
};

View File

@ -7,14 +7,15 @@
"generators": {},
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"executor": "@nx/webpack:webpack",
"options": {
"outputPath": "dist/apps/api",
"main": "apps/api/src/main.ts",
"tsConfig": "apps/api/tsconfig.app.json",
"assets": ["apps/api/src/assets"],
"target": "node",
"compiler": "tsc",
"deleteOutputPath": false,
"main": "apps/api/src/main.ts",
"outputPath": "dist/apps/api",
"sourceMap": true,
"target": "node",
"tsConfig": "apps/api/tsconfig.app.json",
"webpackConfig": "apps/api/webpack.config.js"
},
"configurations": {
@ -33,6 +34,26 @@
},
"outputs": ["{options.outputPath}"]
},
"copy-assets": {
"executor": "nx:run-commands",
"options": {
"commands": [
{
"command": "shx rm -rf dist/apps/api"
},
{
"command": "shx mkdir -p dist/apps/api/assets/locales"
},
{
"command": "shx cp -r apps/api/src/assets/* dist/apps/api/assets"
},
{
"command": "shx cp -r apps/client/src/locales/* dist/apps/api/assets/locales"
}
],
"parallel": false
}
},
"serve": {
"executor": "@nx/js:node",
"options": {
@ -40,7 +61,7 @@
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"executor": "@nx/eslint:lint",
"options": {
"lintFilePatterns": ["apps/api/**/*.ts"]
}

View File

@ -1,6 +1,10 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { Access } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
@ -24,11 +28,12 @@ import { CreateAccessDto } from './create-access.dto';
export class AccessController {
public constructor(
private readonly accessService: AccessService,
private readonly configurationService: ConfigurationService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get()
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getAllAccesses(): Promise<Access[]> {
const accessesWithGranteeUser = await this.accessService.accesses({
include: {
@ -38,32 +43,38 @@ export class AccessController {
where: { userId: this.request.user.id }
});
return accessesWithGranteeUser.map((access) => {
if (access.GranteeUser) {
return accessesWithGranteeUser.map(
({ alias, GranteeUser, id, permissions }) => {
if (GranteeUser) {
return {
alias,
id,
permissions,
grantee: GranteeUser?.id,
type: 'PRIVATE'
};
}
return {
alias: access.alias,
grantee: access.GranteeUser?.id,
id: access.id,
type: 'RESTRICTED_VIEW'
alias,
id,
permissions,
grantee: 'Public',
type: 'PUBLIC'
};
}
return {
alias: access.alias,
grantee: 'Public',
id: access.id,
type: 'PUBLIC'
};
});
);
}
@HasPermission(permissions.createAccess)
@Post()
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createAccess(
@Body() data: CreateAccessDto
): Promise<AccessModel> {
if (
!hasPermission(this.request.user.permissions, permissions.createAccess)
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
this.request.user.subscription.type === 'Basic'
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
@ -71,25 +82,30 @@ export class AccessController {
);
}
return this.accessService.createAccess({
alias: data.alias || undefined,
GranteeUser: data.granteeUserId
? { connect: { id: data.granteeUserId } }
: undefined,
User: { connect: { id: this.request.user.id } }
});
try {
return this.accessService.createAccess({
alias: data.alias || undefined,
GranteeUser: data.granteeUserId
? { connect: { id: data.granteeUserId } }
: undefined,
permissions: data.permissions,
User: { connect: { id: this.request.user.id } }
});
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.BAD_REQUEST),
StatusCodes.BAD_REQUEST
);
}
}
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAccess)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteAccess(@Param('id') id: string): Promise<AccessModel> {
const access = await this.accessService.access({ id });
if (
!hasPermission(this.request.user.permissions, permissions.deleteAccess) ||
!access ||
access.userId !== this.request.user.id
) {
if (!access || access.userId !== this.request.user.id) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN

View File

@ -1,4 +1,6 @@
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
import { Module } from '@nestjs/common';
import { AccessController } from './access.controller';
@ -7,7 +9,7 @@ import { AccessService } from './access.service';
@Module({
controllers: [AccessController],
exports: [AccessService],
imports: [PrismaModule],
imports: [ConfigurationModule, PrismaModule],
providers: [AccessService]
})
export class AccessModule {}

View File

@ -1,5 +1,6 @@
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { AccessWithGranteeUser } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { Access, Prisma } from '@prisma/client';

View File

@ -1,4 +1,5 @@
import { IsOptional, IsString } from 'class-validator';
import { AccessPermission } from '@prisma/client';
import { IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
export class CreateAccessDto {
@IsOptional()
@ -6,10 +7,10 @@ export class CreateAccessDto {
alias?: string;
@IsOptional()
@IsString()
@IsUUID()
granteeUserId?: string;
@IsEnum(AccessPermission, { each: true })
@IsOptional()
@IsString()
type?: 'PUBLIC';
permissions?: AccessPermission[];
}

View File

@ -1,7 +1,13 @@
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
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';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Body,
Post,
Delete,
HttpException,
Inject,
@ -14,31 +20,56 @@ 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'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteAccountBalance(
@Param('id') id: string
): Promise<AccountBalance> {
const accountBalance = await this.accountBalanceService.accountBalance({
id
id,
userId: this.request.user.id
});
if (
!hasPermission(
this.request.user.permissions,
permissions.deleteAccountBalance
) ||
!accountBalance ||
accountBalance.userId !== this.request.user.id
) {
if (!accountBalance) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
@ -46,7 +77,8 @@ export class AccountBalanceController {
}
return this.accountBalanceService.deleteAccountBalance({
id
id: accountBalance.id,
userId: accountBalance.userId
});
}
}

View File

@ -1,5 +1,7 @@
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';
import { Module } from '@nestjs/common';
import { AccountBalanceController } from './account-balance.controller';
@ -9,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,13 +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
) {}
@ -23,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';
@ -79,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

@ -1,17 +1,22 @@
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { RedactValuesInResponseInterceptor } from '@ghostfolio/api/interceptors/redact-values-in-response.interceptor';
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/redact-values-in-response.interceptor';
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { ImpersonationService } from '@ghostfolio/api/services/impersonation/impersonation.service';
import { HEADER_KEY_IMPERSONATION } from '@ghostfolio/common/config';
import {
AccountBalancesResponse,
Accounts
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type {
AccountWithValue,
RequestWithUser
} from '@ghostfolio/common/types';
import {
Body,
Controller,
@ -23,6 +28,7 @@ import {
Param,
Post,
Put,
Query,
UseGuards,
UseInterceptors
} from '@nestjs/common';
@ -41,23 +47,16 @@ 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
) {}
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAccount)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteAccount(@Param('id') id: string): Promise<AccountModel> {
if (
!hasPermission(this.request.user.permissions, permissions.deleteAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const account = await this.accountService.accountWithOrders(
{
id_userId: {
@ -68,41 +67,47 @@ export class AccountController {
{ Order: true }
);
if (account?.isDefault || account?.Order.length > 0) {
if (!account || account?.Order.length > 0) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.accountService.deleteAccount(
{
id_userId: {
id,
userId: this.request.user.id
}
},
this.request.user.id
);
return this.accountService.deleteAccount({
id_userId: {
id,
userId: this.request.user.id
}
});
}
@Get()
@UseGuards(AuthGuard('jwt'))
@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
});
}
@Get(':id')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
public async getAccountById(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId,
@ -122,31 +127,24 @@ export class AccountController {
}
@Get(':id/balances')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(RedactValuesInResponseInterceptor)
public async getAccountBalancesById(
@Param('id') id: string
): 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
});
}
@HasPermission(permissions.createAccount)
@Post()
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async createAccount(
@Body() data: CreateAccountDto
): Promise<AccountModel> {
if (
!hasPermission(this.request.user.permissions, permissions.createAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
if (data.platformId) {
const platformId = data.platformId;
delete data.platformId;
@ -172,20 +170,12 @@ export class AccountController {
}
}
@HasPermission(permissions.updateAccount)
@Post('transfer-balance')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async transferAccountBalance(
@Body() { accountIdFrom, accountIdTo, balance }: TransferBalanceDto
) {
if (
!hasPermission(this.request.user.permissions, permissions.updateAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const accountsOfUser = await this.accountService.getAccounts(
this.request.user.id
);
@ -234,18 +224,10 @@ export class AccountController {
});
}
@HasPermission(permissions.updateAccount)
@Put(':id')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async update(@Param('id') id: string, @Body() data: UpdateAccountDto) {
if (
!hasPermission(this.request.user.permissions, permissions.updateAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const originalAccount = await this.accountService.account({
id_userId: {
id,

View File

@ -1,12 +1,12 @@
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';
import { Module } from '@nestjs/common';
import { AccountController } from './account.controller';
@ -17,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,10 +1,15 @@
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 { EventEmitter2 } from '@nestjs/event-emitter';
import { Account, Order, Platform, Prisma } from '@prisma/client';
import Big from 'big.js';
import { Big } from 'big.js';
import { format } from 'date-fns';
import { groupBy } from 'lodash';
import { CashDetails } from './interfaces/cash-details.interface';
@ -13,6 +18,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
) {}
@ -20,10 +26,8 @@ export class AccountService {
public async account({
id_userId
}: Prisma.AccountWhereUniqueInput): Promise<Account | null> {
const { id, userId } = id_userId;
const [account] = await this.accounts({
where: { id, userId }
where: id_userId
});
return account;
@ -86,27 +90,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[]> {
@ -154,12 +169,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) {
@ -197,21 +208,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({
@ -243,17 +259,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,3 +1,5 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { Transform, TransformFnParams } from 'class-transformer';
import {
IsBoolean,
@ -19,7 +21,7 @@ export class CreateAccountDto {
)
comment?: string;
@IsString()
@IsCurrencyCode()
currency: string;
@IsOptional()
@ -34,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,3 +1,5 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { Transform, TransformFnParams } from 'class-transformer';
import {
IsBoolean,
@ -19,7 +21,7 @@ export class UpdateAccountDto {
)
comment?: string;
@IsString()
@IsCurrencyCode()
currency: string;
@IsString()
@ -33,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,27 +1,31 @@
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
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 { 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 {
DATA_GATHERING_QUEUE_PRIORITY_HIGH,
DATA_GATHERING_QUEUE_PRIORITY_MEDIUM,
GATHER_ASSET_PROFILE_PROCESS,
GATHER_ASSET_PROFILE_PROCESS_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 { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { permissions } from '@ghostfolio/common/permissions';
import type {
MarketDataPreset,
RequestWithUser
} from '@ghostfolio/common/types';
import {
Body,
Controller,
@ -29,6 +33,7 @@ import {
Get,
HttpException,
Inject,
Logger,
Param,
Patch,
Post,
@ -54,65 +59,34 @@ export class AdminController {
private readonly adminService: AdminService,
private readonly apiService: ApiService,
private readonly dataGatheringService: DataGatheringService,
private readonly manualService: ManualService,
private readonly marketDataService: MarketDataService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get()
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getAdminData(): Promise<AdminData> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.get();
}
@HasPermission(permissions.accessAdminControl)
@Post('gather')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gather7Days(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
this.dataGatheringService.gather7Days();
}
@HasPermission(permissions.accessAdminControl)
@Post('gather/max')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gatherMax(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
const assetProfileIdentifiers =
await this.dataGatheringService.getAllAssetProfileIdentifiers();
await this.dataGatheringService.addJobsToQueue(
uniqueAssets.map(({ dataSource, symbol }) => {
assetProfileIdentifiers.map(({ dataSource, symbol }) => {
return {
data: {
dataSource,
@ -121,7 +95,8 @@ export class AdminController {
name: GATHER_ASSET_PROFILE_PROCESS,
opts: {
...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol })
jobId: getAssetProfileIdentifier({ dataSource, symbol }),
priority: DATA_GATHERING_QUEUE_PRIORITY_MEDIUM
}
};
})
@ -130,25 +105,15 @@ export class AdminController {
this.dataGatheringService.gatherMax();
}
@HasPermission(permissions.accessAdminControl)
@Post('gather/profile-data')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gatherProfileData(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const uniqueAssets = await this.dataGatheringService.getUniqueAssets();
const assetProfileIdentifiers =
await this.dataGatheringService.getAllAssetProfileIdentifiers();
await this.dataGatheringService.addJobsToQueue(
uniqueAssets.map(({ dataSource, symbol }) => {
assetProfileIdentifiers.map(({ dataSource, symbol }) => {
return {
data: {
dataSource,
@ -157,31 +122,21 @@ export class AdminController {
name: GATHER_ASSET_PROFILE_PROCESS,
opts: {
...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol })
jobId: getAssetProfileIdentifier({ dataSource, symbol }),
priority: DATA_GATHERING_QUEUE_PRIORITY_MEDIUM
}
};
})
);
}
@HasPermission(permissions.accessAdminControl)
@Post('gather/profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gatherProfileDataForSymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
await this.dataGatheringService.addJobToQueue({
data: {
dataSource,
@ -190,53 +145,32 @@ export class AdminController {
name: GATHER_ASSET_PROFILE_PROCESS,
opts: {
...GATHER_ASSET_PROFILE_PROCESS_OPTIONS,
jobId: getAssetProfileIdentifier({ dataSource, symbol })
jobId: getAssetProfileIdentifier({ dataSource, symbol }),
priority: DATA_GATHERING_QUEUE_PRIORITY_HIGH
}
});
}
@Post('gather/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@HasPermission(permissions.accessAdminControl)
public async gatherSymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
this.dataGatheringService.gatherSymbol({ dataSource, symbol });
return;
}
@HasPermission(permissions.accessAdminControl)
@Post('gather/:dataSource/:symbol/:dateString')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async gatherSymbolForDate(
@Param('dataSource') dataSource: DataSource,
@Param('dateString') dateString: string,
@Param('symbol') symbol: string
): Promise<MarketData> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const date = parseISO(dateString);
if (!isDate(date)) {
@ -254,7 +188,8 @@ export class AdminController {
}
@Get('market-data')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getMarketData(
@Query('assetSubClasses') filterByAssetSubClasses?: string,
@Query('presetId') presetId?: MarketDataPreset,
@ -264,18 +199,6 @@ export class AdminController {
@Query('sortDirection') sortDirection?: Prisma.SortOrder,
@Query('take') take?: number
): Promise<AdminMarketData> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAssetSubClasses,
filterBySearchQuery
@ -291,52 +214,62 @@ export class AdminController {
});
}
/**
* @deprecated
*/
@Get('market-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<AdminMarketDataDetails> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.getMarketDataBySymbol({ dataSource, symbol });
}
@HasPermission(permissions.accessAdminControl)
@Post('market-data/:dataSource/:symbol/test')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async testMarketData(
@Body() data: { scraperConfiguration: string },
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<{ price: number }> {
try {
const scraperConfiguration = JSON.parse(data.scraperConfiguration);
const price = await this.manualService.test(scraperConfiguration);
if (price) {
return { price };
}
throw new Error(
`Could not parse the current market price for ${symbol} (${dataSource})`
);
} catch (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'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updateMarketData(
@Body() data: UpdateBulkMarketDataDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const dataBulkUpdate: Prisma.MarketDataUpdateInput[] = data.marketData.map(
({ date, marketPrice }) => ({
dataSource,
marketPrice,
symbol,
date: resetHours(parseISO(date)),
date: parseISO(date),
state: 'CLOSE'
})
);
@ -349,26 +282,15 @@ export class AdminController {
/**
* @deprecated
*/
@HasPermission(permissions.accessAdminControl)
@Put('market-data/:dataSource/:symbol/:dateString')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async update(
@Param('dataSource') dataSource: DataSource,
@Param('dateString') dateString: string,
@Param('symbol') symbol: string,
@Body() data: UpdateMarketDataDto
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const date = parseISO(dateString);
return this.marketDataService.updateMarketData({
@ -383,24 +305,14 @@ export class AdminController {
});
}
@HasPermission(permissions.accessAdminControl)
@Post('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async addProfileData(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<SymbolProfile | never> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.addAssetProfile({
dataSource,
symbol,
@ -409,45 +321,23 @@ export class AdminController {
}
@Delete('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteProfileData(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.deleteProfileData({ dataSource, symbol });
}
@HasPermission(permissions.accessAdminControl)
@Patch('profile-data/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async patchAssetProfileData(
@Body() assetProfileData: UpdateAssetProfileDto,
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<EnhancedSymbolProfile> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.patchAssetProfileData({
...assetProfileData,
dataSource,
@ -455,24 +345,26 @@ export class AdminController {
});
}
@HasPermission(permissions.accessAdminControl)
@Put('settings/:key')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updateProperty(
@Param('key') key: string,
@Body() data: PropertyDto
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.adminService.putSetting(key, data.value);
}
return await 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,13 +1,17 @@
import { OrderModule } from '@ghostfolio/api/app/order/order.module';
import { SubscriptionModule } from '@ghostfolio/api/app/subscription/subscription.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';
import { AdminController } from './admin.controller';
@ -17,16 +21,19 @@ 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,7 @@
import { OrderService } from '@ghostfolio/api/app/order/order.service';
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.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';
@ -13,20 +15,31 @@ import {
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, Injectable, Logger } from '@nestjs/common';
import {
AssetClass,
AssetSubClass,
DataSource,
Prisma,
PrismaClient,
Property,
SymbolProfile
} from '@prisma/client';
@ -36,10 +49,12 @@ 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,
@ -50,7 +65,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({
@ -70,7 +87,7 @@ export class AdminService {
);
}
return await this.symbolProfileService.add(
return this.symbolProfileService.add(
assetProfiles[symbol] as Prisma.SymbolProfileCreateInput
);
} catch (error) {
@ -87,41 +104,51 @@ 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 });
}
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 exchangeRates = this.exchangeRateDataService
.getCurrencies()
.filter((currency) => {
return currency !== DEFAULT_CURRENCY;
})
.map((currency) => {
const label1 = DEFAULT_CURRENCY;
const label2 = currency;
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 {
label1,
label2,
dataSource:
DataSource[
this.configurationService.get('DATA_SOURCE_EXCHANGE_RATES')
],
symbol: `${label1}${label2}`,
value: this.exchangeRateDataService.toCurrency(
1,
DEFAULT_CURRENCY,
currency
)
};
});
const [settings, transactionCount, userCount] = await Promise.all([
this.propertyService.get(),
this.prismaService.order.count(),
this.countUsersWithAnalytics()
]);
return {
exchangeRates,
settings,
transactionCount,
userCount,
version: environment.version
};
}
@ -145,7 +172,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' ||
@ -176,6 +212,7 @@ export class AdminService {
if (searchQuery) {
where.OR = [
{ id: { mode: 'insensitive', startsWith: searchQuery } },
{ isin: { mode: 'insensitive', startsWith: searchQuery } },
{ name: { mode: 'insensitive', startsWith: searchQuery } },
{ symbol: { mode: 'insensitive', startsWith: searchQuery } }
@ -194,101 +231,196 @@ 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,
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 = 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,
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,
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([
{
@ -307,35 +439,85 @@ export class AdminService {
})
]);
if (assetProfile) {
assetProfile.dataProviderInfo = this.dataProviderService
.getDataProvider(assetProfile.dataSource)
.getDataProviderInfo();
}
return {
marketData,
assetProfile: assetProfile ?? {
symbol,
currency: '-'
activitiesCount,
currency,
dataSource,
dateOfFirstActivity,
symbol
}
};
}
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({
assetClass,
assetSubClass,
comment,
countries,
currency,
dataSource,
holdings,
name,
scraperConfiguration,
sectors,
symbol,
symbolMapping
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
await this.symbolProfileService.updateSymbolProfile({
assetClass,
assetSubClass,
symbolMapping,
url
}: AssetProfileIdentifier & Prisma.SymbolProfileUpdateInput) {
const symbolProfileOverrides = {
assetClass: assetClass as AssetClass,
assetSubClass: assetSubClass as AssetSubClass,
name: name as string,
url: url as string
};
const updatedSymbolProfile: AssetProfileIdentifier &
Prisma.SymbolProfileUpdateInput = {
comment,
countries,
currency,
dataSource,
name,
holdings,
scraperConfiguration,
sectors,
symbol,
symbolMapping
});
symbolMapping,
...(dataSource === 'MANUAL'
? { assetClass, assetSubClass, name, url }
: {
SymbolProfileOverrides: {
upsert: {
create: symbolProfileOverrides,
update: symbolProfileOverrides
}
}
})
};
await this.symbolProfileService.updateSymbolProfile(updatedSymbolProfile);
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles([
{
@ -365,15 +547,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: {
Subscription: {
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 (
@ -383,30 +674,43 @@ 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,
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 = {
@ -418,6 +722,8 @@ export class AdminService {
const usersWithAnalytics = await this.prismaService.user.findMany({
orderBy,
skip,
take,
where,
select: {
_count: {
@ -427,18 +733,19 @@ export class AdminService {
select: {
activityCount: true,
country: true,
dataProviderGhostfolioDailyRequests: true,
updatedAt: true
}
},
createdAt: true,
id: true,
role: true,
Subscription: true
},
take: 30
}
});
return usersWithAnalytics.map(
({ _count, Analytics, createdAt, id, Subscription }) => {
({ _count, Analytics, createdAt, id, role, Subscription }) => {
const daysSinceRegistration =
differenceInDays(new Date(), createdAt) + 1;
const engagement = Analytics
@ -448,16 +755,21 @@ export class AdminService {
const subscription = this.configurationService.get(
'ENABLE_FEATURE_SUBSCRIPTION'
)
? this.subscriptionService.getSubscription(Subscription)
? this.subscriptionService.getSubscription({
createdAt,
subscriptions: Subscription
})
: undefined;
return {
createdAt,
engagement,
id,
role,
subscription,
accountCount: _count.Account || 0,
country: Analytics?.country,
dailyApiRequests: Analytics?.dataProviderGhostfolioDailyRequests || 0,
lastActivity: Analytics?.updatedAt,
transactionCount: _count.Order || 0
};

View File

@ -1,87 +1,56 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { AdminJobs } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { permissions } from '@ghostfolio/common/permissions';
import {
Controller,
Delete,
Get,
HttpException,
Inject,
Param,
Query,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { JobStatus } from 'bull';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { QueueService } from './queue.service';
@Controller('admin/queue')
export class QueueController {
public constructor(
private readonly queueService: QueueService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
public constructor(private readonly queueService: QueueService) {}
@Delete('job')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteJobs(
@Query('status') filterByStatus?: string
): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined;
const status = (filterByStatus?.split(',') as JobStatus[]) ?? undefined;
return this.queueService.deleteJobs({ status });
}
@Get('job')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getJobs(
@Query('status') filterByStatus?: string
): Promise<AdminJobs> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
const status = <JobStatus[]>filterByStatus?.split(',') ?? undefined;
const status = (filterByStatus?.split(',') as JobStatus[]) ?? undefined;
return this.queueService.getJobs({ status });
}
@Delete('job/:id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteJob(@Param('id') id: string): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
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,6 @@
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';
import { QueueController } from './queue.controller';
@ -6,7 +8,7 @@ import { QueueService } from './queue.service';
@Module({
controllers: [QueueController],
imports: [DataGatheringModule],
imports: [DataGatheringModule, PortfolioSnapshotQueueModule],
providers: [QueueService]
})
export class QueueModule {}

View File

@ -1,8 +1,10 @@
import {
DATA_GATHERING_QUEUE,
PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE,
QUEUE_JOB_STATUS_LIST
} from '@ghostfolio/common/config';
import { AdminJobs } from '@ghostfolio/common/interfaces';
import { InjectQueue } from '@nestjs/bull';
import { Injectable } from '@nestjs/common';
import { JobStatus, Queue } from 'bull';
@ -11,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({
@ -24,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
@ -38,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;
})
@ -53,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,5 +1,14 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { AssetClass, AssetSubClass, Prisma } from '@prisma/client';
import { IsEnum, IsObject, IsOptional, IsString } from 'class-validator';
import {
IsArray,
IsEnum,
IsObject,
IsOptional,
IsString,
IsUrl
} from 'class-validator';
export class UpdateAssetProfileDto {
@IsEnum(AssetClass, { each: true })
@ -14,6 +23,14 @@ export class UpdateAssetProfileDto {
@IsOptional()
comment?: string;
@IsArray()
@IsOptional()
countries?: Prisma.InputJsonArray;
@IsCurrencyCode()
@IsOptional()
currency?: string;
@IsString()
@IsOptional()
name?: string;
@ -22,9 +39,20 @@ export class UpdateAssetProfileDto {
@IsOptional()
scraperConfiguration?: Prisma.InputJsonObject;
@IsArray()
@IsOptional()
sectors?: Prisma.InputJsonArray;
@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,4 +1,5 @@
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { Controller } from '@nestjs/common';
@Controller()

View File

@ -1,31 +1,42 @@
import { join } from 'path';
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,
SUPPORTED_LANGUAGE_CODES
} from '@ghostfolio/common/config';
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';
import { join } from 'path';
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 { 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 { ExchangeRateModule } from './exchange-rate/exchange-rate.module';
import { ExportModule } from './export/export.module';
import { HealthModule } from './health/health.module';
@ -39,19 +50,23 @@ 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,
AuthDeviceModule,
AuthModule,
BenchmarkModule,
BenchmarksModule,
BullModule.forRoot({
redis: {
db: parseInt(process.env.REDIS_DB ?? '0', 10),
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT ?? '6379', 10),
password: process.env.REDIS_PASSWORD
@ -62,17 +77,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({
@ -102,11 +124,10 @@ import { UserModule } from './user/user.module';
SitemapModule,
SubscriptionModule,
SymbolModule,
TagModule,
TagsModule,
TwitterBotModule,
UserModule
],
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,40 +1,19 @@
import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
Delete,
HttpException,
Inject,
Param,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
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 { Controller, Delete, Param, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Controller('auth-device')
export class AuthDeviceController {
public constructor(
private readonly authDeviceService: AuthDeviceService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
public constructor(private readonly authDeviceService: AuthDeviceService) {}
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
@HasPermission(permissions.deleteAuthDevice)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteAuthDevice(@Param('id') id: string): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.deleteAuthDevice
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
await this.authDeviceService.deleteAuthDevice({ id });
}
}

View File

@ -1,14 +1,13 @@
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';
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,14 +1,11 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { Injectable } from '@nestjs/common';
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

@ -1,7 +1,9 @@
import { WebAuthService } from '@ghostfolio/api/app/auth/web-auth.service';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
import { OAuthResponse } from '@ghostfolio/common/interfaces';
import {
Body,
Controller,
@ -12,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 {
@ -83,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(
@ -118,20 +120,17 @@ export class AuthController {
}
@Get('webauthn/generate-registration-options')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async generateRegistrationOptions() {
return this.webAuthService.generateRegistrationOptions();
}
@Post('webauthn/verify-attestation')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
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,12 +2,15 @@ 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';
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';
@ -27,6 +30,8 @@ import { JwtStrategy } from './jwt.strategy';
UserModule
],
providers: [
ApiKeyService,
ApiKeyStrategy,
AuthDeviceService,
AuthService,
GoogleStrategy,

View File

@ -1,6 +1,7 @@
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Provider } from '@prisma/client';

View File

@ -1,8 +1,9 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
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';
@ -10,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(
@ -19,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

@ -1,4 +1,5 @@
import { AuthDeviceDto } from '@ghostfolio/api/app/auth-device/auth-device.dto';
import { Provider } from '@prisma/client';
export interface AuthDeviceDialogParams {

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,9 +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 { Injectable, UnauthorizedException } from '@nestjs/common';
import { hasRole } from '@ghostfolio/common/permissions';
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()
@ -28,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;
@ -36,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 }
});
@ -44,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

@ -3,6 +3,7 @@ import { AuthDeviceService } from '@ghostfolio/api/app/auth-device/auth-device.s
import { UserService } from '@ghostfolio/api/app/user/user.service';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Inject,
Injectable,
@ -12,16 +13,16 @@ 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 {
@ -40,7 +41,7 @@ export class WebAuthService {
) {}
get rpID() {
return this.configurationService.get('WEB_AUTH_RP_ID');
return new URL(this.configurationService.get('ROOT_URL')).hostname;
}
get expectedOrigin() {
@ -79,7 +80,6 @@ export class WebAuthService {
}
public async verifyAttestation(
deviceName: string,
credential: AttestationCredentialJSON
): Promise<AuthDeviceDto> {
const user = this.request.user;

View File

@ -1,138 +0,0 @@
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 type {
BenchmarkMarketDataDetails,
BenchmarkResponse,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { 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 { BenchmarkService } from './benchmark.service';
@Controller('benchmark')
export class BenchmarkController {
public constructor(
private readonly benchmarkService: BenchmarkService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Post()
@UseGuards(AuthGuard('jwt'))
public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
try {
const benchmark = await this.benchmarkService.addBenchmark({
dataSource,
symbol
});
if (!benchmark) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return benchmark;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Delete(':dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
public async deleteBenchmark(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
try {
const benchmark = await this.benchmarkService.deleteBenchmark({
dataSource,
symbol
});
if (!benchmark) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return benchmark;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get()
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getBenchmark(): Promise<BenchmarkResponse> {
return {
benchmarks: await this.benchmarkService.getBenchmarks()
};
}
@Get(':dataSource/:symbol/:startDateString')
@UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getBenchmarkMarketDataBySymbol(
@Param('dataSource') dataSource: DataSource,
@Param('startDateString') startDateString: string,
@Param('symbol') symbol: string
): Promise<BenchmarkMarketDataDetails> {
const startDate = new Date(startDateString);
return this.benchmarkService.getMarketDataBySymbol({
dataSource,
startDate,
symbol
});
}
}

View File

@ -1,39 +1,19 @@
import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Controller,
HttpException,
Inject,
Post,
UseGuards
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
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 { Controller, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
@Controller('cache')
export class CacheController {
public constructor(
private readonly redisCacheService: RedisCacheService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
public constructor(private readonly redisCacheService: RedisCacheService) {}
@HasPermission(permissions.accessAdminControl)
@Post('flush')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async flushCache(): Promise<void> {
if (
!hasPermission(
this.request.user.permissions,
permissions.accessAdminControl
)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
return this.redisCacheService.reset();
await this.redisCacheService.reset();
}
}

View File

@ -1,24 +1,11 @@
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';
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,39 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import {
DEFAULT_CURRENCY,
DEFAULT_LANGUAGE_CODE
} from '@ghostfolio/common/config';
import { AiPromptResponse } from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Get, Inject, 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,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get('prompt')
@HasPermission(permissions.readAiPrompt)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getPrompt(): Promise<AiPromptResponse> {
const prompt = await this.aiService.getPrompt({
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,51 @@
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 { 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: [
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,60 @@
import { PortfolioService } from '@ghostfolio/api/app/portfolio/portfolio.service';
import { Injectable } from '@nestjs/common';
@Injectable()
export class AiService {
public constructor(private readonly portfolioService: PortfolioService) {}
public async getPrompt({
impersonationId,
languageCode,
userCurrency,
userId
}: {
impersonationId: string;
languageCode: string;
userCurrency: string;
userId: string;
}) {
const { holdings } = await this.portfolioService.getDetails({
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)}% |`;
}
)
];
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,156 @@
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 { 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
} from '@ghostfolio/common/interfaces';
import { permissions } from '@ghostfolio/common/permissions';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
Delete,
Get,
Headers,
HttpException,
Inject,
Param,
Post,
Query,
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 { BenchmarksService } from './benchmarks.service';
@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 }: AssetProfileIdentifier
) {
try {
const benchmark = await this.benchmarkService.addBenchmark({
dataSource,
symbol
});
if (!benchmark) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return benchmark;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Delete(':dataSource/:symbol')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteBenchmark(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
) {
try {
const benchmark = await this.benchmarkService.deleteBenchmark({
dataSource,
symbol
});
if (!benchmark) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return benchmark;
} catch {
throw new HttpException(
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
StatusCodes.INTERNAL_SERVER_ERROR
);
}
}
@Get()
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async getBenchmark(): Promise<BenchmarkResponse> {
return {
benchmarks: await this.benchmarkService.getBenchmarks()
};
}
@Get(':dataSource/:symbol/:startDateString')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
public async getBenchmarkMarketDataForUser(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Param('dataSource') dataSource: DataSource,
@Param('startDateString') startDateString: 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 { endDate, startDate } = getIntervalFromDateRange(
dateRange,
new Date(startDateString)
);
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,
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,375 @@
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 {
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 { 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
) {}
/**
* @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: 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,303 @@
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import {
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 {
DataProviderInfo,
DividendsResponse,
HistoricalResponse,
LookupItem,
LookupResponse,
QuotesResponse
} from '@ghostfolio/common/interfaces';
import { UserWithSettings } from '@ghostfolio/common/types';
import { Injectable, Logger } from '@nestjs/common';
import { DataSource } 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 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,139 @@
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 [
{ 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 = {
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,5 +1,9 @@
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
@ -18,45 +22,76 @@ 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 {
@Controller('tags')
export class TagsController {
public constructor(
@Inject(REQUEST) private readonly request: RequestWithUser,
private readonly tagService: TagService
) {}
@Get()
@UseGuards(AuthGuard('jwt'))
public async getTags() {
return this.tagService.getTagsWithActivityCount();
}
@Post()
@UseGuards(AuthGuard('jwt'))
public async createTag(@Body() data: CreateTagDto): Promise<Tag> {
if (!hasPermission(this.request.user.permissions, permissions.createTag)) {
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);
}
@Put(':id')
@UseGuards(AuthGuard('jwt'))
public async updateTag(@Param('id') id: string, @Body() data: UpdateTagDto) {
if (!hasPermission(this.request.user.permissions, permissions.updateTag)) {
@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();
}
@HasPermission(permissions.updateTag)
@Put(':id')
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async updateTag(@Param('id') id: string, @Body() data: UpdateTagDto) {
const originalTag = await this.tagService.getTag({
id
});
@ -77,28 +112,4 @@ export class TagController {
}
});
}
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
public async deleteTag(@Param('id') id: string) {
if (!hasPermission(this.request.user.permissions, permissions.deleteTag)) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN
);
}
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

@ -1,4 +1,6 @@
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import {
Controller,
Get,
@ -19,7 +21,7 @@ export class ExchangeRateController {
) {}
@Get(':symbol/:dateString')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getExchangeRate(
@Param('dateString') dateString: string,
@Param('symbol') symbol: string

View File

@ -1,4 +1,5 @@
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.module';
import { Module } from '@nestjs/common';
import { ExchangeRateController } from './exchange-rate.controller';

View File

@ -1,4 +1,5 @@
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
import { Injectable } from '@nestjs/common';
@Injectable()

View File

@ -1,5 +1,8 @@
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { Export } from '@ghostfolio/common/interfaces';
import type { RequestWithUser } from '@ghostfolio/common/types';
import { Controller, Get, Inject, Query, UseGuards } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
@ -9,17 +12,28 @@ import { ExportService } from './export.service';
@Controller('export')
export class ExportController {
public constructor(
private readonly apiService: ApiService,
private readonly exportService: ExportService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
@Get()
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async export(
@Query('activityIds') activityIds?: string[]
@Query('accounts') filterByAccounts?: string,
@Query('activityIds') activityIds?: string[],
@Query('assetClasses') filterByAssetClasses?: string,
@Query('tags') filterByTags?: string
): Promise<Export> {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByTags
});
return this.exportService.export({
activityIds,
filters,
userCurrency: this.request.user.Settings.settings.baseCurrency,
userId: this.request.user.id
});

View File

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

View File

@ -1,22 +1,27 @@
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 { Export } from '@ghostfolio/common/interfaces';
import { TagService } from '@ghostfolio/api/services/tag/tag.service';
import { Filter, Export } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
@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({
activityIds,
filters,
userCurrency,
userId
}: {
activityIds?: string[];
filters?: Filter[];
userCurrency: string;
userId: string;
}): Promise<Export> {
@ -42,6 +47,7 @@ export class ExportService {
);
let { activities } = await this.orderService.getOrders({
filters,
userCurrency,
userId,
includeDrafts: true,
@ -56,9 +62,21 @@ export class ExportService {
});
}
const tags = (await this.tagService.getTagsForUser(userId))
.filter(({ isUsed }) => {
return isUsed;
})
.map(({ id, name }) => {
return {
id,
name
};
});
return {
meta: { date: new Date().toISOString(), version: environment.version },
accounts,
tags,
activities: activities.map(
({
accountId,
@ -68,6 +86,7 @@ export class ExportService {
id,
quantity,
SymbolProfile,
tags: currentTags,
type,
unitPrice
}) => {
@ -82,16 +101,18 @@ export class ExportService {
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,12 +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';
@ -16,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,9 @@
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';
import { HealthController } from './health.controller';
@ -8,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,9 @@
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';
@ -7,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) {
@ -17,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

@ -1,5 +1,6 @@
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';

View File

@ -1,9 +1,12 @@
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 { 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 { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { ImportResponse } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import type { RequestWithUser } from '@ghostfolio/common/types';
import {
Body,
Controller,
@ -34,19 +37,18 @@ export class ImportController {
) {}
@Post()
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@HasPermission(permissions.createOrder)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async import(
@Body() importData: ImportDataDto,
@Query('dryRun') isDryRun?: boolean
@Query('dryRun') isDryRunParam = 'false'
): Promise<ImportResponse> {
const isDryRun = isDryRunParam === 'true';
if (
!hasPermission(
this.request.user.permissions,
permissions.createAccount
) ||
!hasPermission(this.request.user.permissions, permissions.createOrder)
!hasPermission(this.request.user.permissions, permissions.createAccount)
) {
throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN),
@ -65,16 +67,13 @@ export class ImportController {
maxActivitiesToImport = Number.MAX_SAFE_INTEGER;
}
const userCurrency = this.request.user.Settings.settings.baseCurrency;
try {
const activities = await this.importService.import({
isDryRun,
maxActivitiesToImport,
userCurrency,
accountsDto: importData.accounts ?? [],
activitiesDto: importData.activities,
userId: this.request.user.id
user: this.request.user
});
return { activities };
@ -92,7 +91,7 @@ export class ImportController {
}
@Get('dividends/:dataSource/:symbol')
@UseGuards(AuthGuard('jwt'))
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor)
public async gatherDividends(

View File

@ -4,12 +4,15 @@ 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';
import { ImportController } from './import.controller';
@ -29,7 +32,9 @@ import { ImportService } from './import.service';
PortfolioModule,
PrismaModule,
RedisCacheModule,
SymbolProfileModule
SymbolProfileModule,
TransformDataSourceInRequestModule,
TransformDataSourceInResponseModule
],
providers: [ImportService]
})

View File

@ -9,25 +9,28 @@ 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
OrderWithAccount,
UserWithSettings
} from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
import Big from 'big.js';
import { Big } from 'big.js';
import { endOfToday, format, isAfter, isSameSecond, parseISO } from 'date-fns';
import { uniqBy } from 'lodash';
import { isNumber, uniqBy } from 'lodash';
import { v4 as uuidv4 } from 'uuid';
@Injectable()
@ -48,7 +51,7 @@ export class ImportService {
dataSource,
symbol,
userCurrency
}: UniqueAsset & { userCurrency: string }): Promise<Activity[]> {
}: AssetProfileIdentifier & { userCurrency: string }): Promise<Activity[]> {
try {
const { firstBuyDate, historicalData, orders } =
await this.portfolioService.getPosition(dataSource, undefined, symbol);
@ -69,65 +72,74 @@ 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,
feeInBaseCurrency: 0,
id: assetProfile.id,
isDraft: false,
SymbolProfile: assetProfile,
symbolProfileId: assetProfile.id,
type: 'DIVIDEND',
unitPrice: marketPrice,
updatedAt: undefined,
userId: Account?.userId,
valueInBaseCurrency:
await this.exchangeRateDataService.toCurrencyAtDate(
value,
assetProfile.currency,
userCurrency,
date
)
};
})
);
} catch {
return [];
}
@ -138,17 +150,16 @@ export class ImportService {
activitiesDto,
isDryRun = false,
maxActivitiesToImport,
userCurrency,
userId
user
}: {
accountsDto: Partial<CreateAccountDto>[];
activitiesDto: Partial<CreateOrderDto>[];
isDryRun?: boolean;
maxActivitiesToImport: number;
userCurrency: string;
userId: string;
user: UserWithSettings;
}): Promise<Activity[]> {
const accountIdMapping: { [oldAccountId: string]: string } = {};
const userCurrency = user.Settings.settings.baseCurrency;
if (!isDryRun && accountsDto?.length) {
const [existingAccounts, existingPlatforms] = await Promise.all([
@ -171,7 +182,7 @@ export class ImportService {
);
// If there is no account or if the account belongs to a different user then create a new account
if (!accountWithSameId || accountWithSameId.userId !== userId) {
if (!accountWithSameId || accountWithSameId.userId !== user.id) {
let oldAccountId: string;
const platformId = account.platformId;
@ -184,7 +195,7 @@ export class ImportService {
let accountObject: Prisma.AccountCreateInput = {
...account,
User: { connect: { id: userId } }
User: { connect: { id: user.id } }
};
if (
@ -200,7 +211,7 @@ export class ImportService {
const newAccount = await this.accountService.createAccount(
accountObject,
userId
user.id
);
// Store the new to old account ID mappings for updating activities
@ -213,7 +224,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 =
@ -231,16 +242,17 @@ export class ImportService {
const assetProfiles = await this.validateActivities({
activitiesDto,
maxActivitiesToImport
maxActivitiesToImport,
user
});
const activitiesExtendedWithErrors = await this.extendActivitiesWithErrors({
activitiesDto,
userCurrency,
userId
userId: user.id
});
const accounts = (await this.accountService.getAccounts(userId)).map(
const accounts = (await this.accountService.getAccounts(user.id)).map(
({ id, name }) => {
return { id, name };
}
@ -254,20 +266,18 @@ export class ImportService {
const activities: Activity[] = [];
for (let [
index,
{
accountId,
comment,
date,
error,
fee,
quantity,
SymbolProfile,
type,
unitPrice
}
] of activitiesExtendedWithErrors.entries()) {
for (const [index, activity] of activitiesExtendedWithErrors.entries()) {
const accountId = activity.accountId;
const comment = activity.comment;
const currency = activity.currency;
const date = activity.date;
const error = activity.error;
let fee = activity.fee;
const quantity = activity.quantity;
const SymbolProfile = activity.SymbolProfile;
const type = activity.type;
let unitPrice = activity.unitPrice;
const assetProfile = assetProfiles[
getAssetProfileIdentifier({
dataSource: SymbolProfile.dataSource,
@ -283,11 +293,12 @@ export class ImportService {
assetSubClass,
countries,
createdAt,
currency,
cusip,
dataSource,
figi,
figiComposite,
figiShareClass,
holdings,
id,
isin,
name,
@ -318,7 +329,7 @@ export class ImportService {
date
);
if (!unitPrice) {
if (!isNumber(unitPrice)) {
throw new Error(
`activities.${index} historical exchange rate at ${format(
date,
@ -340,12 +351,13 @@ export class ImportService {
if (isDryRun) {
order = {
comment,
currency,
date,
fee,
quantity,
type,
unitPrice,
userId,
Account: validatedAccount,
accountId: validatedAccount?.id,
accountUserId: undefined,
createdAt: new Date(),
@ -356,11 +368,12 @@ export class ImportService {
assetSubClass,
countries,
createdAt,
currency,
cusip,
dataSource,
figi,
figiComposite,
figiShareClass,
holdings,
id,
isin,
name,
@ -370,11 +383,13 @@ 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()
updatedAt: new Date(),
userId: user.id
};
} else {
if (error) {
@ -388,14 +403,14 @@ export class ImportService {
quantity,
type,
unitPrice,
userId,
accountId: validatedAccount?.id,
SymbolProfile: {
connectOrCreate: {
create: {
currency,
dataSource,
symbol
symbol,
currency: assetProfile.currency,
userId: dataSource === 'MANUAL' ? user.id : undefined
},
where: {
dataSource_symbol: {
@ -406,8 +421,14 @@ export class ImportService {
}
},
updateAccountBalance: false,
User: { connect: { id: userId } }
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();
@ -416,18 +437,21 @@ export class ImportService {
...order,
error,
value,
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
feeInBaseCurrency: await this.exchangeRateDataService.toCurrencyAtDate(
fee,
currency,
userCurrency
assetProfile.currency,
userCurrency,
date
),
// @ts-ignore
SymbolProfile: assetProfile,
valueInBaseCurrency: this.exchangeRateDataService.toCurrency(
value,
currency,
userCurrency
)
valueInBaseCurrency:
await this.exchangeRateDataService.toCurrencyAtDate(
value,
assetProfile.currency,
userCurrency,
date
)
});
}
@ -444,15 +468,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;
@ -467,12 +492,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(
({
@ -519,22 +545,15 @@ export class ImportService {
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,
holdings: undefined,
id: undefined,
isin: null,
name: null,
scraperConfiguration: null,
sectors: null,
symbolMapping: null,
updatedAt: undefined,
url: null
sectors: undefined,
updatedAt: undefined
}
};
}
@ -553,10 +572,12 @@ export class ImportService {
private async validateActivities({
activitiesDto,
maxActivitiesToImport
maxActivitiesToImport,
user
}: {
activitiesDto: Partial<CreateOrderDto>[];
maxActivitiesToImport: number;
user: UserWithSettings;
}) {
if (activitiesDto?.length > maxActivitiesToImport) {
throw new Error(`Too many activities (${maxActivitiesToImport} at most)`);
@ -565,47 +586,55 @@ export class ImportService {
const assetProfiles: {
[assetProfileIdentifier: string]: Partial<SymbolProfile>;
} = {};
const uniqueActivitiesDto = uniqBy(
activitiesDto,
({ dataSource, symbol }) => {
return getAssetProfileIdentifier({ dataSource, symbol });
}
);
const dataSources = await this.dataProviderService.getDataSources();
for (const [
index,
{ currency, dataSource, symbol }
] of uniqueActivitiesDto.entries()) {
if (!this.configurationService.get('DATA_SOURCES').includes(dataSource)) {
{ currency, dataSource, symbol, type }
] of activitiesDto.entries()) {
if (!dataSources.includes(dataSource)) {
throw new Error(
`activities.${index}.dataSource ("${dataSource}") is not valid`
);
}
if (dataSource !== 'MANUAL') {
const assetProfile = (
await this.dataProviderService.getAssetProfiles([
{ dataSource, symbol }
])
)?.[symbol];
if (
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
user.subscription.type === 'Basic'
) {
const dataProvider = this.dataProviderService.getDataProvider(
DataSource[dataSource]
);
if (!assetProfile?.name) {
if (dataProvider.getDataProviderInfo().isPremium) {
throw new Error(
`activities.${index}.symbol ("${symbol}") is not valid for the specified data source ("${dataSource}")`
`activities.${index}.dataSource ("${dataSource}") is not valid`
);
}
}
if (
assetProfile.currency !== currency &&
!this.exchangeRateDataService.hasCurrencyPair(
currency,
assetProfile.currency
)
) {
throw new Error(
`activities.${index}.currency ("${currency}") does not match with "${assetProfile.currency}" and no exchange rate is available from "${currency}" to "${assetProfile.currency}"`
);
if (!assetProfiles[getAssetProfileIdentifier({ dataSource, symbol })]) {
const assetProfile = {
currency,
...(
await this.dataProviderService.getAssetProfiles([
{ dataSource, symbol }
])
)?.[symbol]
};
if (type === 'BUY' || type === 'DIVIDEND' || type === 'SELL') {
if (!assetProfile?.name) {
throw new Error(
`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,5 +1,6 @@
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';
import { InfoService } from './info.service';

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 { 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,7 +32,7 @@ import { InfoService } from './info.service';
PropertyModule,
RedisCacheModule,
SymbolProfileModule,
TagModule,
TransformDataSourceInResponseModule,
UserModule
],
providers: [InfoService]

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