diff --git a/.prettierrc b/.prettierrc index 30f191d91..6a8ad9afa 100644 --- a/.prettierrc +++ b/.prettierrc @@ -9,6 +9,7 @@ ], "attributeSort": "ASC", "endOfLine": "auto", + "plugins": ["prettier-plugin-organize-attributes"], "printWidth": 80, "singleQuote": true, "tabWidth": 2, diff --git a/CHANGELOG.md b/CHANGELOG.md index 546e67dfd..111a6ae57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.304.0 - 2023-08-27 + +### Added + +- Added health check endpoints for data enhancers + +### Changed + +- Upgraded `Nx` from version `16.7.2` to `16.7.4` +- Upgraded `prettier` from version `2.8.4` to `3.0.2` + ## 1.303.0 - 2023-08-23 ### Added diff --git a/apps/api/src/app/auth/auth.controller.ts b/apps/api/src/app/auth/auth.controller.ts index 0c6b047bf..376109b8d 100644 --- a/apps/api/src/app/auth/auth.controller.ts +++ b/apps/api/src/app/auth/auth.controller.ts @@ -41,9 +41,8 @@ export class AuthController { @Param('accessToken') accessToken: string ): Promise { try { - const authToken = await this.authService.validateAnonymousLogin( - accessToken - ); + const authToken = + await this.authService.validateAnonymousLogin(accessToken); return { authToken }; } catch { throw new HttpException( diff --git a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts b/apps/api/src/app/exchange-rate/exchange-rate.controller.ts index 40ff9d3c8..8e01c4ca9 100644 --- a/apps/api/src/app/exchange-rate/exchange-rate.controller.ts +++ b/apps/api/src/app/exchange-rate/exchange-rate.controller.ts @@ -7,10 +7,10 @@ import { UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; +import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { ExchangeRateService } from './exchange-rate.service'; -import { parseISO } from 'date-fns'; @Controller('exchange-rate') export class ExchangeRateController { diff --git a/apps/api/src/app/health/health.controller.ts b/apps/api/src/app/health/health.controller.ts index d9af97981..cc430c0dc 100644 --- a/apps/api/src/app/health/health.controller.ts +++ b/apps/api/src/app/health/health.controller.ts @@ -18,6 +18,19 @@ export class HealthController { @Get() public async getHealth() {} + @Get('data-enhancer/:name') + public async getHealthOfDataEnhancer(@Param('name') name: string) { + const hasResponse = + await this.healthService.hasResponseFromDataEnhancer(name); + + if (hasResponse !== true) { + throw new HttpException( + getReasonPhrase(StatusCodes.SERVICE_UNAVAILABLE), + StatusCodes.SERVICE_UNAVAILABLE + ); + } + } + @Get('data-provider/:dataSource') @UseInterceptors(TransformDataSourceInRequestInterceptor) public async getHealthOfDataProvider( @@ -30,9 +43,8 @@ export class HealthController { ); } - const hasResponse = await this.healthService.hasResponseFromDataProvider( - dataSource - ); + const hasResponse = + await this.healthService.hasResponseFromDataProvider(dataSource); if (hasResponse !== true) { throw new HttpException( diff --git a/apps/api/src/app/health/health.module.ts b/apps/api/src/app/health/health.module.ts index 1c5292027..b6952c3b5 100644 --- a/apps/api/src/app/health/health.module.ts +++ b/apps/api/src/app/health/health.module.ts @@ -1,4 +1,5 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.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 { Module } from '@nestjs/common'; @@ -7,7 +8,7 @@ import { HealthService } from './health.service'; @Module({ controllers: [HealthController], - imports: [ConfigurationModule, DataProviderModule], + imports: [ConfigurationModule, DataEnhancerModule, DataProviderModule], providers: [HealthService] }) export class HealthModule {} diff --git a/apps/api/src/app/health/health.service.ts b/apps/api/src/app/health/health.service.ts index afbcc0a74..8fac2dde9 100644 --- a/apps/api/src/app/health/health.service.ts +++ b/apps/api/src/app/health/health.service.ts @@ -1,3 +1,4 @@ +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 { Injectable } from '@nestjs/common'; import { DataSource } from '@prisma/client'; @@ -5,9 +6,14 @@ import { DataSource } from '@prisma/client'; @Injectable() export class HealthService { public constructor( + private readonly dataEnhancerService: DataEnhancerService, private readonly dataProviderService: DataProviderService ) {} + public async hasResponseFromDataEnhancer(aName: string) { + return this.dataEnhancerService.enhance(aName); + } + public async hasResponseFromDataProvider(aDataSource: DataSource) { return this.dataProviderService.checkQuote(aDataSource); } diff --git a/apps/api/src/app/portfolio/portfolio.service.ts b/apps/api/src/app/portfolio/portfolio.service.ts index b80cc95d7..47857c050 100644 --- a/apps/api/src/app/portfolio/portfolio.service.ts +++ b/apps/api/src/app/portfolio/portfolio.service.ts @@ -470,9 +470,8 @@ export class PortfolioService { transactionPoints[0]?.date ?? format(new Date(), DATE_FORMAT) ); const startDate = this.getStartDate(dateRange, portfolioStart); - const currentPositions = await portfolioCalculator.getCurrentPositions( - startDate - ); + const currentPositions = + await portfolioCalculator.getCurrentPositions(startDate); const cashDetails = await this.accountService.getCashDetails({ filters, @@ -887,9 +886,8 @@ export class PortfolioService { const transactionPoints = portfolioCalculator.getTransactionPoints(); const portfolioStart = parseDate(transactionPoints[0].date); - const currentPositions = await portfolioCalculator.getCurrentPositions( - portfolioStart - ); + const currentPositions = + await portfolioCalculator.getCurrentPositions(portfolioStart); const position = currentPositions.positions.find( (item) => item.symbol === aSymbol @@ -1135,9 +1133,8 @@ export class PortfolioService { const portfolioStart = parseDate(transactionPoints[0].date); const startDate = this.getStartDate(dateRange, portfolioStart); - const currentPositions = await portfolioCalculator.getCurrentPositions( - startDate - ); + const currentPositions = + await portfolioCalculator.getCurrentPositions(startDate); const positions = currentPositions.positions.filter( (item) => !item.quantity.eq(0) @@ -1327,9 +1324,8 @@ export class PortfolioService { portfolioCalculator.setTransactionPoints(transactionPoints); const portfolioStart = parseDate(transactionPoints[0].date); - const currentPositions = await portfolioCalculator.getCurrentPositions( - portfolioStart - ); + const currentPositions = + await portfolioCalculator.getCurrentPositions(portfolioStart); const positions = currentPositions.positions.filter( (item) => !item.quantity.eq(0) diff --git a/apps/api/src/app/subscription/subscription.service.ts b/apps/api/src/app/subscription/subscription.service.ts index c3e01851d..d94dd68ad 100644 --- a/apps/api/src/app/subscription/subscription.service.ts +++ b/apps/api/src/app/subscription/subscription.service.ts @@ -93,9 +93,8 @@ export class SubscriptionService { public async createSubscriptionViaStripe(aCheckoutSessionId: string) { try { - const session = await this.stripe.checkout.sessions.retrieve( - aCheckoutSessionId - ); + const session = + await this.stripe.checkout.sessions.retrieve(aCheckoutSessionId); await this.createSubscription({ price: session.amount_total / 100, diff --git a/apps/api/src/app/symbol/symbol.controller.ts b/apps/api/src/app/symbol/symbol.controller.ts index e09b08f5b..ad9042991 100644 --- a/apps/api/src/app/symbol/symbol.controller.ts +++ b/apps/api/src/app/symbol/symbol.controller.ts @@ -15,13 +15,13 @@ import { import { REQUEST } from '@nestjs/core'; import { AuthGuard } from '@nestjs/passport'; import { DataSource } from '@prisma/client'; +import { parseISO } from 'date-fns'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { isDate, isEmpty } from 'lodash'; import { LookupItem } from './interfaces/lookup-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface'; import { SymbolService } from './symbol.service'; -import { parseISO } from 'date-fns'; @Controller('symbol') export class SymbolController { diff --git a/apps/api/src/services/data-gathering/data-gathering.service.ts b/apps/api/src/services/data-gathering/data-gathering.service.ts index 7718ef394..77b79835d 100644 --- a/apps/api/src/services/data-gathering/data-gathering.service.ts +++ b/apps/api/src/services/data-gathering/data-gathering.service.ts @@ -127,12 +127,10 @@ export class DataGatheringService { uniqueAssets = await this.getUniqueAssets(); } - const assetProfiles = await this.dataProviderService.getAssetProfiles( - uniqueAssets - ); - const symbolProfiles = await this.symbolProfileService.getSymbolProfiles( - uniqueAssets - ); + const assetProfiles = + await this.dataProviderService.getAssetProfiles(uniqueAssets); + const symbolProfiles = + await this.symbolProfileService.getSymbolProfiles(uniqueAssets); for (const [symbol, assetProfile] of Object.entries(assetProfiles)) { const symbolMapping = symbolProfiles.find((symbolProfile) => { diff --git a/apps/api/src/services/data-provider/data-enhancer/data-enhancer.module.ts b/apps/api/src/services/data-provider/data-enhancer/data-enhancer.module.ts index a658ef448..069309508 100644 --- a/apps/api/src/services/data-provider/data-enhancer/data-enhancer.module.ts +++ b/apps/api/src/services/data-provider/data-enhancer/data-enhancer.module.ts @@ -4,14 +4,18 @@ import { TrackinsightDataEnhancerService } from '@ghostfolio/api/services/data-p import { YahooFinanceDataEnhancerService } from '@ghostfolio/api/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service'; import { Module } from '@nestjs/common'; +import { DataEnhancerService } from './data-enhancer.service'; + @Module({ exports: [ - 'DataEnhancers', + DataEnhancerService, TrackinsightDataEnhancerService, - YahooFinanceDataEnhancerService + YahooFinanceDataEnhancerService, + 'DataEnhancers' ], imports: [ConfigurationModule, CryptocurrencyModule], providers: [ + DataEnhancerService, TrackinsightDataEnhancerService, YahooFinanceDataEnhancerService, { diff --git a/apps/api/src/services/data-provider/data-enhancer/data-enhancer.service.ts b/apps/api/src/services/data-provider/data-enhancer/data-enhancer.service.ts new file mode 100644 index 000000000..e5038c7c6 --- /dev/null +++ b/apps/api/src/services/data-provider/data-enhancer/data-enhancer.service.ts @@ -0,0 +1,44 @@ +import { DataEnhancerInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-enhancer.interface'; +import { HttpException, Inject, Injectable } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; +import { StatusCodes, getReasonPhrase } from 'http-status-codes'; + +@Injectable() +export class DataEnhancerService { + public constructor( + @Inject('DataEnhancers') + private readonly dataEnhancers: DataEnhancerInterface[] + ) {} + + public async enhance(aName: string) { + const dataEnhancer = this.dataEnhancers.find((dataEnhancer) => { + return dataEnhancer.getName() === aName; + }); + + if (!dataEnhancer) { + throw new HttpException( + getReasonPhrase(StatusCodes.NOT_FOUND), + StatusCodes.NOT_FOUND + ); + } + + try { + const assetProfile = await dataEnhancer.enhance({ + response: { + assetClass: 'EQUITY', + assetSubClass: 'ETF' + }, + symbol: dataEnhancer.getTestSymbol() + }); + + if ( + (assetProfile.countries as unknown as Prisma.JsonArray)?.length > 0 && + (assetProfile.sectors as unknown as Prisma.JsonArray)?.length > 0 + ) { + return true; + } + } catch {} + + return false; + } +} diff --git a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts index 2c9f1b7a8..07c0234b6 100644 --- a/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/trackinsight/trackinsight.service.ts @@ -38,9 +38,9 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { .json() .catch(() => { return got( - `${TrackinsightDataEnhancerService.baseUrl}/funds/${ - symbol.split('.')?.[0] - }.json` + `${TrackinsightDataEnhancerService.baseUrl}/funds/${symbol.split( + '.' + )?.[0]}.json` ) .json() .catch(() => { @@ -60,9 +60,9 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { .json() .catch(() => { return got( - `${TrackinsightDataEnhancerService.baseUrl}/holdings/${ - symbol.split('.')?.[0] - }.json` + `${TrackinsightDataEnhancerService.baseUrl}/holdings/${symbol.split( + '.' + )?.[0]}.json` ) .json() .catch(() => { @@ -126,4 +126,8 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface { public getName() { return 'TRACKINSIGHT'; } + + public getTestSymbol() { + return 'QQQ'; + } } diff --git a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts index 3ad69646b..9e7032de6 100644 --- a/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts +++ b/apps/api/src/services/data-provider/data-enhancer/yahoo-finance/yahoo-finance.service.ts @@ -99,9 +99,8 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { yahooSymbol = quotes[0].symbol; } - const { countries, sectors, url } = await this.getAssetProfile( - yahooSymbol - ); + const { countries, sectors, url } = + await this.getAssetProfile(yahooSymbol); if (countries) { response.countries = countries; @@ -234,6 +233,10 @@ export class YahooFinanceDataEnhancerService implements DataEnhancerInterface { return DataSource.YAHOO; } + public getTestSymbol() { + return 'AAPL'; + } + public parseAssetClass({ quoteType, shortName diff --git a/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts b/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts index 4e5ce8cba..9c6db9196 100644 --- a/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts +++ b/apps/api/src/services/data-provider/interfaces/data-enhancer.interface.ts @@ -10,4 +10,6 @@ export interface DataEnhancerInterface { }): Promise>; getName(): string; + + getTestSymbol(): string; } diff --git a/apps/api/src/services/twitter-bot/twitter-bot.service.ts b/apps/api/src/services/twitter-bot/twitter-bot.service.ts index 218dd291f..d3e7fb91c 100644 --- a/apps/api/src/services/twitter-bot/twitter-bot.service.ts +++ b/apps/api/src/services/twitter-bot/twitter-bot.service.ts @@ -65,9 +65,8 @@ export class TwitterBotService { status += benchmarkListing; } - const { data: createdTweet } = await this.twitterClient.v2.tweet( - status - ); + const { data: createdTweet } = + await this.twitterClient.v2.tweet(status); Logger.log( `Fear & Greed Index has been tweeted: https://twitter.com/ghostfolio_/status/${createdTweet.id}`, diff --git a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html index dc64ed120..d6f67faa3 100644 --- a/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html +++ b/apps/client/src/app/components/admin-market-data-detail/admin-market-data-detail.component.html @@ -29,7 +29,7 @@ }" [title]=" (itemByMonth.key + '-' + (i + 1 < 10 ? '0' + (i + 1) : i + 1) - | date : defaultDateFormat) ?? '' + | date: defaultDateFormat) ?? '' " (click)=" onOpenMarketDataDetail({ diff --git a/apps/client/src/app/pages/about/about-page-routing.module.ts b/apps/client/src/app/pages/about/about-page-routing.module.ts index 88c8080ed..ac586e069 100644 --- a/apps/client/src/app/pages/about/about-page-routing.module.ts +++ b/apps/client/src/app/pages/about/about-page-routing.module.ts @@ -1,10 +1,11 @@ +import * as path from 'path'; + import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { paths } from '@ghostfolio/client/app-routing.module'; import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; import { AboutPageComponent } from './about-page.component'; -import { paths } from '@ghostfolio/client/app-routing.module'; -import * as path from 'path'; const routes: Routes = [ { diff --git a/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.html b/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.html index 9f4dc39df..0c147ded3 100644 --- a/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.html +++ b/apps/client/src/app/pages/blog/2023/07/exploring-the-path-to-fire/exploring-the-path-to-fire-page.html @@ -10,7 +10,7 @@
2023-07-01
Exploring the Path to Financial Independence and Retiring Early (FIRE) Teaser diff --git a/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.html b/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.html index 16cb3d72e..b7ce5b67e 100644 --- a/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.html +++ b/apps/client/src/app/pages/blog/2023/08/ghostfolio-joins-oss-friends/ghostfolio-joins-oss-friends-page.html @@ -157,7 +157,7 @@ aria-current="page" class="active breadcrumb-item text-truncate" > - Ghostfolio meets Umbrel + Ghostfolio joins OSS Friends diff --git a/apps/client/src/index.html b/apps/client/src/index.html index 55120ec08..eabc42f94 100644 --- a/apps/client/src/index.html +++ b/apps/client/src/index.html @@ -1,4 +1,4 @@ - + ${title} diff --git a/libs/ui/.storybook/preview-head.html b/libs/ui/.storybook/preview-head.html index ef2b9647a..aa97d5363 100644 --- a/libs/ui/.storybook/preview-head.html +++ b/libs/ui/.storybook/preview-head.html @@ -1,6 +1,6 @@