Feature/provide data provider info in search (#2958)
* Provide data provider info in search * Update changelog
This commit is contained in:
parent
06ba7a4b1b
commit
893e76f83f
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Extended the assistant by an asset class selector (experimental)
|
- Extended the assistant by an asset class selector (experimental)
|
||||||
|
- Added the data provider information to the search endpoint
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -64,16 +64,13 @@ export class ImportController {
|
|||||||
maxActivitiesToImport = Number.MAX_SAFE_INTEGER;
|
maxActivitiesToImport = Number.MAX_SAFE_INTEGER;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const activities = await this.importService.import({
|
const activities = await this.importService.import({
|
||||||
isDryRun,
|
isDryRun,
|
||||||
maxActivitiesToImport,
|
maxActivitiesToImport,
|
||||||
userCurrency,
|
|
||||||
accountsDto: importData.accounts ?? [],
|
accountsDto: importData.accounts ?? [],
|
||||||
activitiesDto: importData.activities,
|
activitiesDto: importData.activities,
|
||||||
userId: this.request.user.id
|
user: this.request.user
|
||||||
});
|
});
|
||||||
|
|
||||||
return { activities };
|
return { activities };
|
||||||
|
@ -21,7 +21,8 @@ import {
|
|||||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||||
import {
|
import {
|
||||||
AccountWithPlatform,
|
AccountWithPlatform,
|
||||||
OrderWithAccount
|
OrderWithAccount,
|
||||||
|
UserWithSettings
|
||||||
} from '@ghostfolio/common/types';
|
} from '@ghostfolio/common/types';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
|
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
|
||||||
@ -138,17 +139,16 @@ export class ImportService {
|
|||||||
activitiesDto,
|
activitiesDto,
|
||||||
isDryRun = false,
|
isDryRun = false,
|
||||||
maxActivitiesToImport,
|
maxActivitiesToImport,
|
||||||
userCurrency,
|
user
|
||||||
userId
|
|
||||||
}: {
|
}: {
|
||||||
accountsDto: Partial<CreateAccountDto>[];
|
accountsDto: Partial<CreateAccountDto>[];
|
||||||
activitiesDto: Partial<CreateOrderDto>[];
|
activitiesDto: Partial<CreateOrderDto>[];
|
||||||
isDryRun?: boolean;
|
isDryRun?: boolean;
|
||||||
maxActivitiesToImport: number;
|
maxActivitiesToImport: number;
|
||||||
userCurrency: string;
|
user: UserWithSettings;
|
||||||
userId: string;
|
|
||||||
}): Promise<Activity[]> {
|
}): Promise<Activity[]> {
|
||||||
const accountIdMapping: { [oldAccountId: string]: string } = {};
|
const accountIdMapping: { [oldAccountId: string]: string } = {};
|
||||||
|
const userCurrency = user.Settings.settings.baseCurrency;
|
||||||
|
|
||||||
if (!isDryRun && accountsDto?.length) {
|
if (!isDryRun && accountsDto?.length) {
|
||||||
const [existingAccounts, existingPlatforms] = await Promise.all([
|
const [existingAccounts, existingPlatforms] = await Promise.all([
|
||||||
@ -171,7 +171,7 @@ export class ImportService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// If there is no account or if the account belongs to a different user then create a new account
|
// If there is no account or if the account belongs to a different user then create a new account
|
||||||
if (!accountWithSameId || accountWithSameId.userId !== userId) {
|
if (!accountWithSameId || accountWithSameId.userId !== user.id) {
|
||||||
let oldAccountId: string;
|
let oldAccountId: string;
|
||||||
const platformId = account.platformId;
|
const platformId = account.platformId;
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ export class ImportService {
|
|||||||
|
|
||||||
let accountObject: Prisma.AccountCreateInput = {
|
let accountObject: Prisma.AccountCreateInput = {
|
||||||
...account,
|
...account,
|
||||||
User: { connect: { id: userId } }
|
User: { connect: { id: user.id } }
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -200,7 +200,7 @@ export class ImportService {
|
|||||||
|
|
||||||
const newAccount = await this.accountService.createAccount(
|
const newAccount = await this.accountService.createAccount(
|
||||||
accountObject,
|
accountObject,
|
||||||
userId
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
// Store the new to old account ID mappings for updating activities
|
// Store the new to old account ID mappings for updating activities
|
||||||
@ -231,16 +231,17 @@ export class ImportService {
|
|||||||
|
|
||||||
const assetProfiles = await this.validateActivities({
|
const assetProfiles = await this.validateActivities({
|
||||||
activitiesDto,
|
activitiesDto,
|
||||||
maxActivitiesToImport
|
maxActivitiesToImport,
|
||||||
|
user
|
||||||
});
|
});
|
||||||
|
|
||||||
const activitiesExtendedWithErrors = await this.extendActivitiesWithErrors({
|
const activitiesExtendedWithErrors = await this.extendActivitiesWithErrors({
|
||||||
activitiesDto,
|
activitiesDto,
|
||||||
userCurrency,
|
userCurrency,
|
||||||
userId
|
userId: user.id
|
||||||
});
|
});
|
||||||
|
|
||||||
const accounts = (await this.accountService.getAccounts(userId)).map(
|
const accounts = (await this.accountService.getAccounts(user.id)).map(
|
||||||
({ id, name }) => {
|
({ id, name }) => {
|
||||||
return { id, name };
|
return { id, name };
|
||||||
}
|
}
|
||||||
@ -345,7 +346,6 @@ export class ImportService {
|
|||||||
quantity,
|
quantity,
|
||||||
type,
|
type,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
userId,
|
|
||||||
accountId: validatedAccount?.id,
|
accountId: validatedAccount?.id,
|
||||||
accountUserId: undefined,
|
accountUserId: undefined,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
@ -374,7 +374,8 @@ export class ImportService {
|
|||||||
},
|
},
|
||||||
Account: validatedAccount,
|
Account: validatedAccount,
|
||||||
symbolProfileId: undefined,
|
symbolProfileId: undefined,
|
||||||
updatedAt: new Date()
|
updatedAt: new Date(),
|
||||||
|
userId: user.id
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -388,7 +389,6 @@ export class ImportService {
|
|||||||
quantity,
|
quantity,
|
||||||
type,
|
type,
|
||||||
unitPrice,
|
unitPrice,
|
||||||
userId,
|
|
||||||
accountId: validatedAccount?.id,
|
accountId: validatedAccount?.id,
|
||||||
SymbolProfile: {
|
SymbolProfile: {
|
||||||
connectOrCreate: {
|
connectOrCreate: {
|
||||||
@ -406,7 +406,8 @@ export class ImportService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateAccountBalance: false,
|
updateAccountBalance: false,
|
||||||
User: { connect: { id: userId } }
|
User: { connect: { id: user.id } },
|
||||||
|
userId: user.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -553,10 +554,12 @@ export class ImportService {
|
|||||||
|
|
||||||
private async validateActivities({
|
private async validateActivities({
|
||||||
activitiesDto,
|
activitiesDto,
|
||||||
maxActivitiesToImport
|
maxActivitiesToImport,
|
||||||
|
user
|
||||||
}: {
|
}: {
|
||||||
activitiesDto: Partial<CreateOrderDto>[];
|
activitiesDto: Partial<CreateOrderDto>[];
|
||||||
maxActivitiesToImport: number;
|
maxActivitiesToImport: number;
|
||||||
|
user: UserWithSettings;
|
||||||
}) {
|
}) {
|
||||||
if (activitiesDto?.length > maxActivitiesToImport) {
|
if (activitiesDto?.length > maxActivitiesToImport) {
|
||||||
throw new Error(`Too many activities (${maxActivitiesToImport} at most)`);
|
throw new Error(`Too many activities (${maxActivitiesToImport} at most)`);
|
||||||
@ -583,6 +586,21 @@ export class ImportService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||||
|
user.subscription.type === 'Basic'
|
||||||
|
) {
|
||||||
|
const dataProvider = this.dataProviderService.getDataProvider(
|
||||||
|
DataSource[dataSource]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dataProvider.getDataProviderInfo().isPremium) {
|
||||||
|
throw new Error(
|
||||||
|
`activities.${index}.dataSource ("${dataSource}") is not valid`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const assetProfile = {
|
const assetProfile = {
|
||||||
currency,
|
currency,
|
||||||
...(
|
...(
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
|
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
|
||||||
|
|
||||||
export interface LookupItem {
|
export interface LookupItem {
|
||||||
assetClass: AssetClass;
|
assetClass: AssetClass;
|
||||||
assetSubClass: AssetSubClass;
|
assetSubClass: AssetSubClass;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
dataProviderInfo: DataProviderInfo;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
name: string;
|
name: string;
|
||||||
symbol: string;
|
symbol: string;
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
IDataProviderResponse
|
IDataProviderResponse
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
import * as Alphavantage from 'alphavantage';
|
import * as Alphavantage from 'alphavantage';
|
||||||
@ -44,6 +45,12 @@ export class AlphaVantageService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({}: GetDividendsParams) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -118,6 +125,7 @@ export class AlphaVantageService implements DataProviderInterface {
|
|||||||
assetClass: undefined,
|
assetClass: undefined,
|
||||||
assetSubClass: undefined,
|
assetSubClass: undefined,
|
||||||
currency: bestMatch['8. currency'],
|
currency: bestMatch['8. currency'],
|
||||||
|
dataProviderInfo: this.getDataProviderInfo(),
|
||||||
dataSource: this.getName(),
|
dataSource: this.getName(),
|
||||||
name: bestMatch['2. name'],
|
name: bestMatch['2. name'],
|
||||||
symbol: bestMatch['1. symbol']
|
symbol: bestMatch['1. symbol']
|
||||||
|
@ -91,6 +91,14 @@ export class CoinGeckoService implements DataProviderInterface {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: false,
|
||||||
|
name: 'CoinGecko',
|
||||||
|
url: 'https://coingecko.com'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({}: GetDividendsParams) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -252,11 +260,4 @@ export class CoinGeckoService implements DataProviderInterface {
|
|||||||
|
|
||||||
return { items };
|
return { items };
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDataProviderInfo(): DataProviderInfo {
|
|
||||||
return {
|
|
||||||
name: 'CoinGecko',
|
|
||||||
url: 'https://coingecko.com'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,31 @@ export class DataProviderService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProvider(providerName: DataSource) {
|
||||||
|
for (const dataProviderInterface of this.dataProviderInterfaces) {
|
||||||
|
if (this.dataProviderMapping[dataProviderInterface.getName()]) {
|
||||||
|
const mappedDataProviderInterface = this.dataProviderInterfaces.find(
|
||||||
|
(currentDataProviderInterface) => {
|
||||||
|
return (
|
||||||
|
currentDataProviderInterface.getName() ===
|
||||||
|
this.dataProviderMapping[dataProviderInterface.getName()]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mappedDataProviderInterface) {
|
||||||
|
return mappedDataProviderInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataProviderInterface.getName() === providerName) {
|
||||||
|
return dataProviderInterface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('No data provider has been found.');
|
||||||
|
}
|
||||||
|
|
||||||
public getDataSourceForExchangeRates(): DataSource {
|
public getDataSourceForExchangeRates(): DataSource {
|
||||||
return DataSource[
|
return DataSource[
|
||||||
this.configurationService.get('DATA_SOURCE_EXCHANGE_RATES')
|
this.configurationService.get('DATA_SOURCE_EXCHANGE_RATES')
|
||||||
@ -520,20 +545,15 @@ export class DataProviderService {
|
|||||||
return { items: lookupItems };
|
return { items: lookupItems };
|
||||||
}
|
}
|
||||||
|
|
||||||
let dataSources = this.configurationService.get('DATA_SOURCES');
|
let dataProviderServices = this.configurationService
|
||||||
|
.get('DATA_SOURCES')
|
||||||
if (
|
.map((dataSource) => {
|
||||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
return this.getDataProvider(DataSource[dataSource]);
|
||||||
user.subscription.type === 'Basic'
|
|
||||||
) {
|
|
||||||
dataSources = dataSources.filter((dataSource) => {
|
|
||||||
return !this.isPremiumDataSource(DataSource[dataSource]);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
for (const dataSource of dataSources) {
|
for (const dataProviderService of dataProviderServices) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.getDataProvider(DataSource[dataSource]).search({
|
dataProviderService.search({
|
||||||
includeIndices,
|
includeIndices,
|
||||||
query
|
query
|
||||||
})
|
})
|
||||||
@ -555,6 +575,16 @@ export class DataProviderService {
|
|||||||
})
|
})
|
||||||
.sort(({ name: name1 }, { name: name2 }) => {
|
.sort(({ name: name1 }, { name: name2 }) => {
|
||||||
return name1?.toLowerCase().localeCompare(name2?.toLowerCase());
|
return name1?.toLowerCase().localeCompare(name2?.toLowerCase());
|
||||||
|
})
|
||||||
|
.map((lookupItem) => {
|
||||||
|
if (
|
||||||
|
!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') ||
|
||||||
|
user.subscription.type === 'Premium'
|
||||||
|
) {
|
||||||
|
lookupItem.dataProviderInfo.isPremium = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lookupItem;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -562,31 +592,6 @@ export class DataProviderService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDataProvider(providerName: DataSource) {
|
|
||||||
for (const dataProviderInterface of this.dataProviderInterfaces) {
|
|
||||||
if (this.dataProviderMapping[dataProviderInterface.getName()]) {
|
|
||||||
const mappedDataProviderInterface = this.dataProviderInterfaces.find(
|
|
||||||
(currentDataProviderInterface) => {
|
|
||||||
return (
|
|
||||||
currentDataProviderInterface.getName() ===
|
|
||||||
this.dataProviderMapping[dataProviderInterface.getName()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mappedDataProviderInterface) {
|
|
||||||
return mappedDataProviderInterface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataProviderInterface.getName() === providerName) {
|
|
||||||
return dataProviderInterface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('No data provider has been found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasCurrency({
|
private hasCurrency({
|
||||||
currency,
|
currency,
|
||||||
dataGatheringItems
|
dataGatheringItems
|
||||||
@ -602,14 +607,6 @@ export class DataProviderService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private isPremiumDataSource(aDataSource: DataSource) {
|
|
||||||
const premiumDataSources: DataSource[] = [
|
|
||||||
DataSource.EOD_HISTORICAL_DATA,
|
|
||||||
DataSource.FINANCIAL_MODELING_PREP
|
|
||||||
];
|
|
||||||
return premiumDataSources.includes(aDataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
private transformHistoricalData({
|
private transformHistoricalData({
|
||||||
allData,
|
allData,
|
||||||
currency,
|
currency,
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
REPLACE_NAME_PARTS
|
REPLACE_NAME_PARTS
|
||||||
} from '@ghostfolio/common/config';
|
} from '@ghostfolio/common/config';
|
||||||
import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper';
|
||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
AssetClass,
|
AssetClass,
|
||||||
@ -58,6 +59,12 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({
|
public async getDividends({
|
||||||
from,
|
from,
|
||||||
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
|
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
|
||||||
@ -312,7 +319,8 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
|||||||
dataSource,
|
dataSource,
|
||||||
name,
|
name,
|
||||||
symbol,
|
symbol,
|
||||||
currency: this.convertCurrency(currency)
|
currency: this.convertCurrency(currency),
|
||||||
|
dataProviderInfo: this.getDataProviderInfo()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -45,6 +45,14 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: true,
|
||||||
|
name: 'Financial Modeling Prep',
|
||||||
|
url: 'https://financialmodelingprep.com/developer/docs'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({}: GetDividendsParams) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -202,11 +210,4 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
|||||||
|
|
||||||
return { items };
|
return { items };
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDataProviderInfo(): DataProviderInfo {
|
|
||||||
return {
|
|
||||||
name: 'Financial Modeling Prep',
|
|
||||||
url: 'https://financialmodelingprep.com/developer/docs'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
||||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@ -40,6 +41,12 @@ export class GoogleSheetsService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({}: GetDividendsParams) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -177,7 +184,11 @@ export class GoogleSheetsService implements DataProviderInterface {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { items };
|
return {
|
||||||
|
items: items.map((item) => {
|
||||||
|
return { ...item, dataProviderInfo: this.getDataProviderInfo() };
|
||||||
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSheet({
|
private async getSheet({
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
IDataProviderHistoricalResponse,
|
IDataProviderHistoricalResponse,
|
||||||
IDataProviderResponse
|
IDataProviderResponse
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { Granularity } from '@ghostfolio/common/types';
|
import { Granularity } from '@ghostfolio/common/types';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
|
|
||||||
@ -11,6 +12,8 @@ export interface DataProviderInterface {
|
|||||||
|
|
||||||
getAssetProfile(aSymbol: string): Promise<Partial<SymbolProfile>>;
|
getAssetProfile(aSymbol: string): Promise<Partial<SymbolProfile>>;
|
||||||
|
|
||||||
|
getDataProviderInfo(): DataProviderInfo;
|
||||||
|
|
||||||
getDividends({ from, granularity, symbol, to }: GetDividendsParams): Promise<{
|
getDividends({ from, granularity, symbol, to }: GetDividendsParams): Promise<{
|
||||||
[date: string]: IDataProviderHistoricalResponse;
|
[date: string]: IDataProviderHistoricalResponse;
|
||||||
}>;
|
}>;
|
||||||
|
@ -18,7 +18,10 @@ import {
|
|||||||
extractNumberFromString,
|
extractNumberFromString,
|
||||||
getYesterday
|
getYesterday
|
||||||
} from '@ghostfolio/common/helper';
|
} from '@ghostfolio/common/helper';
|
||||||
import { ScraperConfiguration } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
DataProviderInfo,
|
||||||
|
ScraperConfiguration
|
||||||
|
} from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
import * as cheerio from 'cheerio';
|
import * as cheerio from 'cheerio';
|
||||||
@ -59,6 +62,12 @@ export class ManualService implements DataProviderInterface {
|
|||||||
return assetProfile;
|
return assetProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({}: GetDividendsParams) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -214,7 +223,11 @@ export class ManualService implements DataProviderInterface {
|
|||||||
return !isUUID(symbol);
|
return !isUUID(symbol);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { items };
|
return {
|
||||||
|
items: items.map((item) => {
|
||||||
|
return { ...item, dataProviderInfo: this.getDataProviderInfo() };
|
||||||
|
})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async test(scraperConfiguration: ScraperConfiguration) {
|
public async test(scraperConfiguration: ScraperConfiguration) {
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
|
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
|
||||||
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
|
||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@ -37,6 +38,12 @@ export class RapidApiService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({}: GetDividendsParams) {
|
public async getDividends({}: GetDividendsParams) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
|
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
|
||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
|
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||||
import { addDays, format, isSameDay } from 'date-fns';
|
import { addDays, format, isSameDay } from 'date-fns';
|
||||||
@ -47,6 +48,12 @@ export class YahooFinanceService implements DataProviderInterface {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDataProviderInfo(): DataProviderInfo {
|
||||||
|
return {
|
||||||
|
isPremium: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public async getDividends({
|
public async getDividends({
|
||||||
from,
|
from,
|
||||||
granularity = 'day',
|
granularity = 'day',
|
||||||
@ -283,6 +290,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
|||||||
assetSubClass,
|
assetSubClass,
|
||||||
symbol,
|
symbol,
|
||||||
currency: marketDataItem.currency,
|
currency: marketDataItem.currency,
|
||||||
|
dataProviderInfo: this.getDataProviderInfo(),
|
||||||
dataSource: this.getName(),
|
dataSource: this.getName(),
|
||||||
name: this.yahooFinanceDataEnhancerService.formatName({
|
name: this.yahooFinanceDataEnhancerService.formatName({
|
||||||
longName: quote.longname,
|
longName: quote.longname,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
export interface DataProviderInfo {
|
export interface DataProviderInfo {
|
||||||
name: string;
|
isPremium: boolean;
|
||||||
url: string;
|
name?: string;
|
||||||
|
url?: string;
|
||||||
}
|
}
|
||||||
|
@ -129,8 +129,8 @@
|
|||||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
||||||
<ng-container i18n>Name</ng-container>
|
<ng-container i18n>Name</ng-container>
|
||||||
</th>
|
</th>
|
||||||
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
|
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||||
<div class="d-flex align-items-center">
|
<div class="align-items-center d-flex line-height-1">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
||||||
<span
|
<span
|
||||||
|
@ -15,12 +15,15 @@
|
|||||||
<mat-option
|
<mat-option
|
||||||
*ngFor="let lookupItem of filteredLookupItems"
|
*ngFor="let lookupItem of filteredLookupItems"
|
||||||
class="line-height-1"
|
class="line-height-1"
|
||||||
|
[disabled]="lookupItem.dataProviderInfo.isPremium"
|
||||||
[value]="lookupItem"
|
[value]="lookupItem"
|
||||||
>
|
>
|
||||||
<span
|
<span class="align-items-center d-flex line-height-1"
|
||||||
><b>{{ lookupItem.name }}</b></span
|
><b>{{ lookupItem.name }}</b>
|
||||||
>
|
@if (lookupItem.dataProviderInfo.isPremium) {
|
||||||
<br />
|
<gf-premium-indicator class="ml-1" [enableLink]="false" />
|
||||||
|
}
|
||||||
|
</span>
|
||||||
<small class="text-muted"
|
<small class="text-muted"
|
||||||
>{{ lookupItem.symbol | gfSymbol }} · {{ lookupItem.currency
|
>{{ lookupItem.symbol | gfSymbol }} · {{ lookupItem.currency
|
||||||
}}<ng-container *ngIf="lookupItem.assetSubClass">
|
}}<ng-container *ngIf="lookupItem.assetSubClass">
|
||||||
|
@ -7,6 +7,7 @@ import { MatInputModule } from '@angular/material/input';
|
|||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { SymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete/symbol-autocomplete.component';
|
import { SymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete/symbol-autocomplete.component';
|
||||||
|
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [SymbolAutocompleteComponent],
|
declarations: [SymbolAutocompleteComponent],
|
||||||
@ -14,6 +15,7 @@ import { SymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete/
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
GfPremiumIndicatorModule,
|
||||||
GfSymbolModule,
|
GfSymbolModule,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user