From dab91540921a8eea950057ff6a178ccfbdb52739 Mon Sep 17 00:00:00 2001 From: Kevin Viet Date: Thu, 23 Feb 2023 18:09:35 +0100 Subject: [PATCH 1/4] Add support for armv7 processors (#1738) * Add support for armv7 processors * Update changelog --- .github/workflows/docker-image.yml | 2 +- CHANGELOG.md | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 6b85f639b..26ccb9b8d 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -41,7 +41,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - platforms: linux/amd64,linux/arm64 + platforms: linux/amd64,linux/arm/v7,linux/arm64 push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.output.labels }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 96aafcb82..569f7d128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ 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). +## Unreleased + +### Added + +- Added the configuration to publish a `linux/arm/v7` docker image + ## 1.237.0 - 2023-02-19 ### Added @@ -757,7 +763,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the alias to the `Access` database schema - Added support for translated time distances -- Added a _GitHub Action_ to create an `arm64` docker image +- Added a _GitHub Action_ to create an `linux/arm64` docker image ### Changed From 494ba36d449a6a738fb418b2ffa77183c090cb98 Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Fri, 24 Feb 2023 20:44:20 +0100 Subject: [PATCH 2/4] Feature/reset letter spacing in buttons (#1742) * Reset letter spacing in buttons * Update changelog --- CHANGELOG.md | 4 ++++ apps/client/src/styles/theme.scss | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 569f7d128..239f057e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added the configuration to publish a `linux/arm/v7` docker image +### Fixed + +- Reset the letter spacing in buttons + ## 1.237.0 - 2023-02-19 ### Added diff --git a/apps/client/src/styles/theme.scss b/apps/client/src/styles/theme.scss index a6f6107c2..6a538caa2 100644 --- a/apps/client/src/styles/theme.scss +++ b/apps/client/src/styles/theme.scss @@ -69,6 +69,8 @@ $gf-secondary: ( ) ); +$gf-typography: mat.define-typography-config(); + @include mat.core(); @include mat.legacy-core(); @@ -80,7 +82,7 @@ $gf-theme-default: mat.define-light-theme( accent: mat.define-palette($gf-secondary, 500, 900, A100) ), density: 0, - typography: mat.define-typography-config() + typography: $gf-typography ) ); @include mat.all-component-themes($gf-theme-default); @@ -94,7 +96,7 @@ $gf-theme-dark: mat.define-dark-theme( accent: mat.define-palette($gf-secondary, 500, 900, A100) ), density: 0, - typography: mat.define-typography-config() + typography: $gf-typography ) ); .is-dark-theme { @@ -107,4 +109,6 @@ $gf-theme-dark: mat.define-dark-theme( --gf-theme-primary-500-rgb: 0, 187, 255; --gf-theme-secondary-500: #3686cf; --gf-theme-secondary-500-rgb: 78, 208, 94; + + --mdc-typography-button-letter-spacing: normal; } From 80d06389220e595f8bcf8caa1036f5933be26c90 Mon Sep 17 00:00:00 2001 From: Mirio Date: Sat, 25 Feb 2023 09:45:01 +0100 Subject: [PATCH 3/4] Adding Coingecko Data Provider (#1736) * Adding Coingecko Data Provider * Update changelog --------- Co-authored-by: Thomas <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 1 + apps/api/src/app/order/order.service.ts | 3 - .../coingecko/coingecko.service.ts | 191 ++++++++++++++++++ .../data-provider/data-provider.module.ts | 5 + .../migration.sql | 2 + prisma/schema.prisma | 1 + 6 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/services/data-provider/coingecko/coingecko.service.ts create mode 100644 prisma/migrations/20230222200407_added_coingecko_to_data_source/migration.sql diff --git a/CHANGELOG.md b/CHANGELOG.md index 239f057e3..087480e8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added `COINGECKO` as a new data source type - Added the configuration to publish a `linux/arm/v7` docker image ### Fixed diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 077f2227b..2d0cb7376 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -110,9 +110,6 @@ export class OrderService { dataSource, symbol: id }; - } else { - data.SymbolProfile.connectOrCreate.create.symbol = - data.SymbolProfile.connectOrCreate.create.symbol.toUpperCase(); } await this.dataGatheringService.addJobToQueue( diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts new file mode 100644 index 000000000..7bd852f5d --- /dev/null +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -0,0 +1,191 @@ +import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface'; +import { ConfigurationService } from '@ghostfolio/api/services/configuration.service'; +import { DataProviderInterface } from '@ghostfolio/api/services/data-provider/interfaces/data-provider.interface'; +import { + IDataProviderHistoricalResponse, + IDataProviderResponse +} from '@ghostfolio/api/services/interfaces/interfaces'; +import { DATE_FORMAT } from '@ghostfolio/common/helper'; +import { Granularity } from '@ghostfolio/common/types'; +import { Injectable, Logger } from '@nestjs/common'; +import { + AssetClass, + AssetSubClass, + DataSource, + SymbolProfile +} from '@prisma/client'; +import bent from 'bent'; +import { format, fromUnixTime, getUnixTime } from 'date-fns'; + +@Injectable() +export class CoinGeckoService implements DataProviderInterface { + private baseCurrency: string; + private readonly URL = 'https://api.coingecko.com/api/v3'; + + public constructor( + private readonly configurationService: ConfigurationService + ) { + this.baseCurrency = this.configurationService.get('BASE_CURRENCY'); + } + + public canHandle(symbol: string) { + return true; + } + + public async getAssetProfile( + aSymbol: string + ): Promise> { + const response: Partial = { + assetClass: AssetClass.CASH, + assetSubClass: AssetSubClass.CRYPTOCURRENCY, + currency: this.baseCurrency, + dataSource: this.getName(), + symbol: aSymbol + }; + + try { + const get = bent(`${this.URL}/coins/${aSymbol}`, 'GET', 'json', 200); + const { name } = await get(); + + response.name = name; + } catch (error) { + Logger.error(error, 'CoinGeckoService'); + } + + return response; + } + + public async getDividends({ + from, + granularity = 'day', + symbol, + to + }: { + from: Date; + granularity: Granularity; + symbol: string; + to: Date; + }) { + return {}; + } + + public async getHistorical( + aSymbol: string, + aGranularity: Granularity = 'day', + from: Date, + to: Date + ): Promise<{ + [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + }> { + try { + const get = bent( + `${ + this.URL + }/coins/${aSymbol}/market_chart/range?vs_currency=${this.baseCurrency.toLowerCase()}&from=${getUnixTime( + from + )}&to=${getUnixTime(to)}`, + 'GET', + 'json', + 200 + ); + const { prices } = await get(); + + const result: { + [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + } = { + [aSymbol]: {} + }; + + for (const [timestamp, marketPrice] of prices) { + result[aSymbol][format(fromUnixTime(timestamp / 1000), DATE_FORMAT)] = { + marketPrice + }; + } + + return result; + } catch (error) { + throw new Error( + `Could not get historical market data for ${aSymbol} (${this.getName()}) from ${format( + from, + DATE_FORMAT + )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` + ); + } + } + + public getMaxNumberOfSymbolsPerRequest() { + return 50; + } + + public getName(): DataSource { + return DataSource.COINGECKO; + } + + public async getQuotes( + aSymbols: string[] + ): Promise<{ [symbol: string]: IDataProviderResponse }> { + const results: { [symbol: string]: IDataProviderResponse } = {}; + + if (aSymbols.length <= 0) { + return {}; + } + + try { + const get = bent( + `${this.URL}/simple/price?ids=${aSymbols.join( + ',' + )}&vs_currencies=${this.baseCurrency.toLowerCase()}`, + 'GET', + 'json', + 200 + ); + const response = await get(); + + for (const symbol in response) { + if (Object.prototype.hasOwnProperty.call(response, symbol)) { + results[symbol] = { + currency: this.baseCurrency, + dataSource: DataSource.COINGECKO, + marketPrice: response[symbol][this.baseCurrency.toLowerCase()], + marketState: 'open' + }; + } + } + } catch (error) { + Logger.error(error, 'CoinGeckoService'); + } + + return results; + } + + public async search(aQuery: string): Promise<{ items: LookupItem[] }> { + let items: LookupItem[] = []; + + if (aQuery.length <= 2) { + return { items }; + } + + try { + const get = bent( + `${this.URL}/search?query=${aQuery}`, + 'GET', + 'json', + 200 + ); + const { coins } = await get(); + + items = coins.map(({ id: symbol, name }) => { + return { + name, + symbol, + currency: this.baseCurrency, + dataSource: this.getName() + }; + }); + } catch (error) { + Logger.error(error, 'CoinGeckoService'); + } + + return { items }; + } +} diff --git a/apps/api/src/services/data-provider/data-provider.module.ts b/apps/api/src/services/data-provider/data-provider.module.ts index abafe6189..d36a12aa6 100644 --- a/apps/api/src/services/data-provider/data-provider.module.ts +++ b/apps/api/src/services/data-provider/data-provider.module.ts @@ -1,6 +1,7 @@ import { ConfigurationModule } from '@ghostfolio/api/services/configuration.module'; 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 { EodHistoricalDataService } from '@ghostfolio/api/services/data-provider/eod-historical-data/eod-historical-data.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'; @@ -21,6 +22,7 @@ import { DataProviderService } from './data-provider.service'; ], providers: [ AlphaVantageService, + CoinGeckoService, DataProviderService, EodHistoricalDataService, GoogleSheetsService, @@ -30,6 +32,7 @@ import { DataProviderService } from './data-provider.service'; { inject: [ AlphaVantageService, + CoinGeckoService, EodHistoricalDataService, GoogleSheetsService, ManualService, @@ -39,6 +42,7 @@ import { DataProviderService } from './data-provider.service'; provide: 'DataProviderInterfaces', useFactory: ( alphaVantageService, + coinGeckoService, eodHistoricalDataService, googleSheetsService, manualService, @@ -46,6 +50,7 @@ import { DataProviderService } from './data-provider.service'; yahooFinanceService ) => [ alphaVantageService, + coinGeckoService, eodHistoricalDataService, googleSheetsService, manualService, diff --git a/prisma/migrations/20230222200407_added_coingecko_to_data_source/migration.sql b/prisma/migrations/20230222200407_added_coingecko_to_data_source/migration.sql new file mode 100644 index 000000000..2b40d220a --- /dev/null +++ b/prisma/migrations/20230222200407_added_coingecko_to_data_source/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "DataSource" ADD VALUE 'COINGECKO'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 937897a1d..dc4c1302d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -205,6 +205,7 @@ enum AssetSubClass { enum DataSource { ALPHA_VANTAGE + COINGECKO EOD_HISTORICAL_DATA GOOGLE_SHEETS MANUAL From aef840c2ccc73090bff5dc66aaedf7890df60bca Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sat, 25 Feb 2023 10:15:25 +0100 Subject: [PATCH 4/4] Bugfix/fix maximum call stack size exceeded error in value redaction (#1743) * Bugfix for RangeError: Maximum call stack size exceeded * Update changelog --- CHANGELOG.md | 1 + apps/api/src/helper/object.helper.ts | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 087480e8d..b2c0912f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Fixed `RangeError: Maximum call stack size exceeded` for values of type `Big` in the value redaction interceptor for the impersonation mode - Reset the letter spacing in buttons ## 1.237.0 - 2023-02-19 diff --git a/apps/api/src/helper/object.helper.ts b/apps/api/src/helper/object.helper.ts index 7ee07b468..6db53b0a1 100644 --- a/apps/api/src/helper/object.helper.ts +++ b/apps/api/src/helper/object.helper.ts @@ -1,3 +1,4 @@ +import Big from 'big.js'; import { cloneDeep, isArray, isObject } from 'lodash'; export function hasNotDefinedValuesInObject(aObject: Object): boolean { @@ -59,7 +60,10 @@ export function redactAttributes({ return redactAttributes({ options, object: currentObject }); } ); - } else if (isObject(redactedObject[property])) { + } else if ( + isObject(redactedObject[property]) && + !(redactedObject[property] instanceof Big) + ) { // Recursively call the function on the nested object redactedObject[property] = redactAttributes({ options,