Browse Source

Merge branch 'main' into feature/upgrade-zone.js-to-version-0.12.0

pull/1740/head
Thomas Kaul 3 years ago
committed by GitHub
parent
commit
f24ad1fb25
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/docker-image.yml
  2. 12
      CHANGELOG.md
  3. 3
      apps/api/src/app/order/order.service.ts
  4. 6
      apps/api/src/helper/object.helper.ts
  5. 191
      apps/api/src/services/data-provider/coingecko/coingecko.service.ts
  6. 5
      apps/api/src/services/data-provider/data-provider.module.ts
  7. 8
      apps/client/src/styles/theme.scss
  8. 2
      prisma/migrations/20230222200407_added_coingecko_to_data_source/migration.sql
  9. 1
      prisma/schema.prisma

2
.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 }}

12
CHANGELOG.md

@ -7,10 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added `COINGECKO` as a new data source type
- Added the configuration to publish a `linux/arm/v7` docker image
### Changed
- Upgraded `zone.js` from version `0.11.8` to `0.12.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
### Added
@ -763,7 +773,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

3
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(

6
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,

191
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<Partial<SymbolProfile>> {
const response: Partial<SymbolProfile> = {
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 };
}
}

5
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,

8
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;
}

2
prisma/migrations/20230222200407_added_coingecko_to_data_source/migration.sql

@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "DataSource" ADD VALUE 'COINGECKO';

1
prisma/schema.prisma

@ -205,6 +205,7 @@ enum AssetSubClass {
enum DataSource {
ALPHA_VANTAGE
COINGECKO
EOD_HISTORICAL_DATA
GOOGLE_SHEETS
MANUAL

Loading…
Cancel
Save