diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d0c6d82..5ffe1bc45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Improved the language localization for Catalan (`ca`) - Improved the language localization for Turkish (`tr`) +### Fixed + +- Fixed an issue with the locale in the scraper configuration + ## 2.174.0 - 2025-06-24 ### Added diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 82524ef9b..8531cf95c 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -17,7 +17,8 @@ import { AdminData, AdminMarketData, AdminUsers, - EnhancedSymbolProfile + EnhancedSymbolProfile, + ScraperConfiguration } from '@ghostfolio/common/interfaces'; import { permissions } from '@ghostfolio/common/permissions'; import type { @@ -222,13 +223,12 @@ export class AdminController { @Post('market-data/:dataSource/:symbol/test') @UseGuards(AuthGuard('jwt'), HasPermissionGuard) public async testMarketData( - @Body() data: { scraperConfiguration: string }, + @Body() data: { scraperConfiguration: ScraperConfiguration }, @Param('dataSource') dataSource: DataSource, @Param('symbol') symbol: string ): Promise<{ price: number }> { try { - const scraperConfiguration = JSON.parse(data.scraperConfiguration); - const price = await this.manualService.test(scraperConfiguration); + const price = await this.manualService.test(data.scraperConfiguration); if (price) { return { price }; diff --git a/apps/api/src/services/data-provider/manual/manual.service.ts b/apps/api/src/services/data-provider/manual/manual.service.ts index 66e625e47..bcaa77a17 100644 --- a/apps/api/src/services/data-provider/manual/manual.service.ts +++ b/apps/api/src/services/data-provider/manual/manual.service.ts @@ -104,7 +104,7 @@ export class ManualService implements DataProviderInterface { } return historical; - } else if (selector === undefined || url === undefined) { + } else if (!selector || !url) { return {}; } @@ -162,7 +162,11 @@ export class ManualService implements DataProviderInterface { const symbolProfilesWithScraperConfigurationAndInstantMode = symbolProfiles.filter(({ scraperConfiguration }) => { - return scraperConfiguration?.mode === 'instant'; + return ( + scraperConfiguration?.mode === 'instant' && + scraperConfiguration?.selector && + scraperConfiguration?.url + ); }); const scraperResultPromises = diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index d9b344699..c52d9137d 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -11,6 +11,7 @@ import { AdminMarketDataDetails, AssetProfileIdentifier, LineChartItem, + ScraperConfiguration, User } from '@ghostfolio/common/interfaces'; import { translate } from '@ghostfolio/ui/i18n'; @@ -41,6 +42,7 @@ import { AssetClass, AssetSubClass, MarketData, + Prisma, SymbolProfile } from '@prisma/client'; import { format } from 'date-fns'; @@ -343,7 +345,10 @@ export class AssetProfileDialog implements OnDestroy, OnInit { public async onSubmitAssetProfileForm() { let countries = []; - let scraperConfiguration = {}; + let scraperConfiguration: ScraperConfiguration = { + selector: '', + url: '' + }; let sectors = []; let symbolMapping = {}; @@ -354,9 +359,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit { try { scraperConfiguration = { defaultMarketPrice: - this.assetProfileForm.controls['scraperConfiguration'].controls[ + (this.assetProfileForm.controls['scraperConfiguration'].controls[ 'defaultMarketPrice' - ].value, + ].value as number) || undefined, headers: JSON.parse( this.assetProfileForm.controls['scraperConfiguration'].controls[ 'headers' @@ -365,10 +370,10 @@ export class AssetProfileDialog implements OnDestroy, OnInit { locale: this.assetProfileForm.controls['scraperConfiguration'].controls[ 'locale' - ].value, + ].value || undefined, mode: this.assetProfileForm.controls['scraperConfiguration'].controls[ 'mode' - ].value, + ].value as ScraperConfiguration['mode'], selector: this.assetProfileForm.controls['scraperConfiguration'].controls[ 'selector' @@ -377,6 +382,10 @@ export class AssetProfileDialog implements OnDestroy, OnInit { 'url' ].value }; + + if (!scraperConfiguration.selector || !scraperConfiguration.url) { + scraperConfiguration = undefined; + } } catch {} try { @@ -391,7 +400,6 @@ export class AssetProfileDialog implements OnDestroy, OnInit { const assetProfile: UpdateAssetProfileDto = { countries, - scraperConfiguration, sectors, symbolMapping, assetClass: this.assetProfileForm.get('assetClass').value, @@ -400,6 +408,8 @@ export class AssetProfileDialog implements OnDestroy, OnInit { currency: this.assetProfileForm.get('currency').value, isActive: this.assetProfileForm.get('isActive').value, name: this.assetProfileForm.get('name').value, + scraperConfiguration: + scraperConfiguration as unknown as Prisma.InputJsonObject, url: this.assetProfileForm.get('url').value || null }; @@ -493,11 +503,10 @@ export class AssetProfileDialog implements OnDestroy, OnInit { this.adminService .testMarketData({ dataSource: this.data.dataSource, - scraperConfiguration: JSON.stringify({ - defaultMarketPrice: - this.assetProfileForm.controls['scraperConfiguration'].controls[ - 'defaultMarketPrice' - ].value, + scraperConfiguration: { + defaultMarketPrice: this.assetProfileForm.controls[ + 'scraperConfiguration' + ].controls['defaultMarketPrice'].value as number, headers: JSON.parse( this.assetProfileForm.controls['scraperConfiguration'].controls[ 'headers' @@ -506,7 +515,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { locale: this.assetProfileForm.controls['scraperConfiguration'].controls[ 'locale' - ].value, + ].value || undefined, mode: this.assetProfileForm.controls['scraperConfiguration'].controls[ 'mode' ].value, @@ -517,7 +526,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { url: this.assetProfileForm.controls['scraperConfiguration'].controls[ 'url' ].value - }), + }, symbol: this.data.symbol }) .pipe( diff --git a/apps/client/src/locales/messages.ca.xlf b/apps/client/src/locales/messages.ca.xlf index 6914f5f80..5b4e696fc 100644 --- a/apps/client/src/locales/messages.ca.xlf +++ b/apps/client/src/locales/messages.ca.xlf @@ -4845,7 +4845,7 @@ Currency Cluster Risks - Currency Cluster Risks + Riscos del clúster de divises apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html 93 @@ -4853,7 +4853,7 @@ Account Cluster Risks - Account Cluster Risks + Riscos del clúster de comptes apps/client/src/app/pages/portfolio/x-ray/x-ray-page.component.html 141 @@ -4861,7 +4861,7 @@ Pricing - Pricing + Preus apps/client/src/app/pages/pricing/pricing-page-routing.module.ts 13 @@ -4873,7 +4873,7 @@ Pricing Plans - Pricing Plans + Plans de preus apps/client/src/app/pages/pricing/pricing-page.html 4 @@ -4881,7 +4881,7 @@ Our official Ghostfolio Premium cloud offering is the easiest way to get started. Due to the time it saves, this will be the best option for most people. Revenue is used to cover the costs of the hosting infrastructure and to fund ongoing development. - Our official Ghostfolio Premium cloud offering is the easiest way to get started. Due to the time it saves, this will be the best option for most people. Revenue is used to cover the costs of the hosting infrastructure and to fund ongoing development. + La nostra oferta oficial al núvol Ghostfolio Premium és la manera més senzilla de començar. A causa del temps que estalvia, aquesta serà la millor opció per a la majoria de la gent. Els ingressos s’utilitzen per cobrir els costos de la infraestructura d’allotjament i per finançar el desenvolupament en curs. apps/client/src/app/pages/pricing/pricing-page.html 6 @@ -4897,7 +4897,7 @@ For tech-savvy investors who prefer to run Ghostfolio on their own infrastructure. - For tech-savvy investors who prefer to run Ghostfolio on their own infrastructure. + Per a inversors experts en tecnologia que prefereixen executar Ghostfolio a la seva pròpia infraestructura. apps/client/src/app/pages/pricing/pricing-page.html 24 @@ -4905,7 +4905,7 @@ Unlimited Transactions - Unlimited Transactions + Transaccions il·limitades apps/client/src/app/pages/pricing/pricing-page.html 31 @@ -4921,7 +4921,7 @@ Unlimited Accounts - Unlimited Accounts + Comptes il·limitats apps/client/src/app/pages/pricing/pricing-page.html 35 @@ -4937,7 +4937,7 @@ Portfolio Performance - Portfolio Performance + Rendiment de la cartera apps/client/src/app/pages/pricing/pricing-page.html 39