Browse Source

Merge branch 'main' into task/migrate-login-dialog-to-form-control

pull/5390/head
Thomas Kaul 15 hours ago
committed by GitHub
parent
commit
271ab0c8ff
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 10
      CHANGELOG.md
  2. 2
      apps/api/src/app/admin/admin.controller.ts
  3. 14
      apps/api/src/app/admin/admin.service.ts
  4. 10
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  5. 8
      apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts
  6. 10
      apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts
  7. 34
      apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts
  8. 2
      apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts
  9. 15
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts
  10. 1
      libs/ui/src/lib/i18n.ts
  11. 12
      package-lock.json
  12. 4
      package.json

10
CHANGELOG.md

@ -7,13 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Changed
- Migrated the login with access token dialog from `ngModel` to form control
## 2.193.0 - 2025-08-22
### Added
- Added a filter by data source for the asset profiles in the admin control panel
- Extended the data providers management of the admin control panel by every data provider in use
### Changed
- Migrated the login with access token dialog from `ngModel` to form control
- Improved the error handling in data providers
- Upgraded `yahoo-finance2` from version `3.4.1` to `3.6.4`
## 2.192.0 - 2025-08-21

2
apps/api/src/app/admin/admin.controller.ts

@ -197,6 +197,7 @@ export class AdminController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getMarketData(
@Query('assetSubClasses') filterByAssetSubClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('presetId') presetId?: MarketDataPreset,
@Query('query') filterBySearchQuery?: string,
@Query('skip') skip?: number,
@ -206,6 +207,7 @@ export class AdminController {
): Promise<AdminMarketData> {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAssetSubClasses,
filterByDataSource,
filterBySearchQuery
});

14
apps/api/src/app/admin/admin.service.ts

@ -218,12 +218,12 @@ export class AdminService {
return type === 'SEARCH_QUERY';
})?.id;
const { ASSET_SUB_CLASS: filtersByAssetSubClass } = groupBy(
filters,
({ type }) => {
const {
ASSET_SUB_CLASS: filtersByAssetSubClass,
DATA_SOURCE: filtersByDataSource
} = groupBy(filters, ({ type }) => {
return type;
}
);
});
const marketDataItems = await this.prismaService.marketData.groupBy({
_count: true,
@ -234,6 +234,10 @@ export class AdminService {
where.assetSubClass = AssetSubClass[filtersByAssetSubClass[0].id];
}
if (filtersByDataSource) {
where.dataSource = DataSource[filtersByDataSource[0].id];
}
if (searchQuery) {
where.OR = [
{ id: { mode: 'insensitive', startsWith: searchQuery } },

10
apps/api/src/services/data-provider/coingecko/coingecko.service.ts

@ -78,7 +78,7 @@ export class CoinGeckoService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
@ -196,8 +196,10 @@ export class CoinGeckoService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the quotes for ${symbols.join(
', '
)} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
}
@ -237,7 +239,7 @@ export class CoinGeckoService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

8
apps/api/src/services/data-provider/eod-historical-data/eod-historical-data.service.ts

@ -282,8 +282,10 @@ export class EodHistoricalDataService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the quotes for ${symbols.join(
', '
)} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;
}
@ -426,7 +428,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to search for ${aQuery} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

10
apps/api/src/services/data-provider/financial-modeling-prep/financial-modeling-prep.service.ts

@ -202,7 +202,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${(
requestTimeout / 1000
).toFixed(3)} seconds`;
@ -392,8 +392,10 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the quotes for ${symbols.join(
', '
)} was aborted because the request to the data provider took more than ${(
requestTimeout / 1000
).toFixed(3)} seconds`;
}
@ -469,7 +471,7 @@ export class FinancialModelingPrepService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

34
apps/api/src/services/data-provider/ghostfolio/ghostfolio.service.ts

@ -68,14 +68,16 @@ export class GhostfolioService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the asset profile for ${symbol} was aborted because the request to the data provider took more than ${(
requestTimeout / 1000
).toFixed(3)} seconds`;
} else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) {
} else if (
error?.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS
) {
message = 'RequestError: The daily request limit has been exceeded';
} else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) {
if (!error.request?.options?.headers?.authorization?.includes('-')) {
} else if (error?.response?.statusCode === StatusCodes.UNAUTHORIZED) {
if (!error?.request?.options?.headers?.authorization?.includes('-')) {
message =
'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.';
} else {
@ -229,14 +231,18 @@ export class GhostfolioService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error.name === 'AbortError') {
message = `RequestError: The operation to get the quotes was aborted because the request to the data provider took more than ${(
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to get the quotes for ${symbols.join(
', '
)} was aborted because the request to the data provider took more than ${(
requestTimeout / 1000
).toFixed(3)} seconds`;
} else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) {
} else if (
error?.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS
) {
message = 'RequestError: The daily request limit has been exceeded';
} else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) {
if (!error.request?.options?.headers?.authorization?.includes('-')) {
} else if (error?.response?.statusCode === StatusCodes.UNAUTHORIZED) {
if (!error?.request?.options?.headers?.authorization?.includes('-')) {
message =
'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.';
} else {
@ -272,14 +278,16 @@ export class GhostfolioService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation to search for ${query} was aborted because the request to the data provider took more than ${(
requestTimeout / 1000
).toFixed(3)} seconds`;
} else if (error.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS) {
} else if (
error?.response?.statusCode === StatusCodes.TOO_MANY_REQUESTS
) {
message = 'RequestError: The daily request limit has been exceeded';
} else if (error.response?.statusCode === StatusCodes.UNAUTHORIZED) {
if (!error.request?.options?.headers?.authorization?.includes('-')) {
if (!error?.request?.options?.headers?.authorization?.includes('-')) {
message =
'RequestError: The provided API key is invalid. Please update it in the Settings section of the Admin Control panel.';
} else {

2
apps/api/src/services/data-provider/rapid-api/rapid-api.service.ts

@ -165,7 +165,7 @@ export class RapidApiService implements DataProviderInterface {
} catch (error) {
let message = error;
if (error?.name === 'AbortError') {
if (['AbortError', 'TimeoutError'].includes(error?.name)) {
message = `RequestError: The operation was aborted because the request to the data provider took more than ${(
this.configurationService.get('REQUEST_TIMEOUT') / 1000
).toFixed(3)} seconds`;

15
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -103,7 +103,8 @@ export class GfAdminMarketDataComponent
@ViewChild(MatSort) sort: MatSort;
public activeFilters: Filter[] = [];
public allFilters: Filter[] = Object.keys(AssetSubClass)
public allFilters: Filter[] = [
...Object.keys(AssetSubClass)
.filter((assetSubClass) => {
return assetSubClass !== 'CASH';
})
@ -113,8 +114,14 @@ export class GfAdminMarketDataComponent
label: translate(assetSubClass),
type: 'ASSET_SUB_CLASS' as Filter['type']
};
})
.concat([
}),
...Object.keys(DataSource).map((dataSource) => {
return {
id: dataSource.toString(),
label: dataSource,
type: 'DATA_SOURCE' as Filter['type']
};
}),
{
id: 'BENCHMARKS',
label: $localize`Benchmarks`,
@ -135,7 +142,7 @@ export class GfAdminMarketDataComponent
label: $localize`ETFs without Sectors`,
type: 'PRESET_ID' as Filter['type']
}
]);
];
public benchmarks: Partial<SymbolProfile>[];
public currentDataSource: DataSource;
public currentSymbol: string;

1
libs/ui/src/lib/i18n.ts

@ -12,6 +12,7 @@ const locales = {
DATA_IMPORT_AND_EXPORT_TOOLTIP_BASIC: $localize`Switch to Ghostfolio Premium or Ghostfolio Open Source easily`,
DATA_IMPORT_AND_EXPORT_TOOLTIP_OSS: $localize`Switch to Ghostfolio Premium easily`,
DATA_IMPORT_AND_EXPORT_TOOLTIP_PREMIUM: $localize`Switch to Ghostfolio Open Source or Ghostfolio Basic easily`,
DATA_SOURCE: $localize`Data Source`,
EMERGENCY_FUND: $localize`Emergency Fund`,
EXCLUDE_FROM_ANALYSIS: $localize`Exclude from Analysis`,
Global: $localize`Global`,

12
package-lock.json

@ -1,12 +1,12 @@
{
"name": "ghostfolio",
"version": "2.192.0",
"version": "2.193.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ghostfolio",
"version": "2.192.0",
"version": "2.193.0",
"hasInstallScript": true,
"license": "AGPL-3.0",
"dependencies": {
@ -93,7 +93,7 @@
"svgmap": "2.12.2",
"twitter-api-v2": "1.23.0",
"uuid": "11.1.0",
"yahoo-finance2": "3.4.1",
"yahoo-finance2": "3.6.4",
"zone.js": "0.15.1"
},
"devDependencies": {
@ -40601,9 +40601,9 @@
}
},
"node_modules/yahoo-finance2": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.4.1.tgz",
"integrity": "sha512-L8Ubmdsn6f+uJEuEDUUHR5n95TFcGkMiMkV0phmvPONFekAn1vWzsEzGfIDG2ODR7aYBB+aURdQg7a3HX2iUHA==",
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/yahoo-finance2/-/yahoo-finance2-3.6.4.tgz",
"integrity": "sha512-IoMU8Hb4BEaNPVnamZjRBuorTGDbaaiV/tM/m3KI8dzwrR6BGmeuT40OX+5IqRiSkMlD8g0kAwGi9E4bY3rLvg==",
"license": "MIT",
"dependencies": {
"@deno/shim-deno": "~0.18.0",

4
package.json

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "2.192.0",
"version": "2.193.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"repository": "https://github.com/ghostfolio/ghostfolio",
@ -139,7 +139,7 @@
"svgmap": "2.12.2",
"twitter-api-v2": "1.23.0",
"uuid": "11.1.0",
"yahoo-finance2": "3.4.1",
"yahoo-finance2": "3.6.4",
"zone.js": "0.15.1"
},
"devDependencies": {

Loading…
Cancel
Save