Feature/add data source as unique constraint to market data schema (#1889)
* Add dataSource as unique constraint to MarketData schema * Update changelog
This commit is contained in:
parent
3dafbf7fef
commit
cddea0401f
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `dataSource` as a unique constraint to the `MarketData` database schema
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Removed the unnecessary sort header of the comment column in the historical market data table of the admin control panel
|
- Removed the unnecessary sort header of the comment column in the historical market data table of the admin control panel
|
||||||
|
@ -319,7 +319,8 @@ export class AdminController {
|
|||||||
return this.marketDataService.updateMarketData({
|
return this.marketDataService.updateMarketData({
|
||||||
data: { ...data, dataSource },
|
data: { ...data, dataSource },
|
||||||
where: {
|
where: {
|
||||||
date_symbol: {
|
dataSource_date_symbol: {
|
||||||
|
dataSource,
|
||||||
date,
|
date,
|
||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,7 @@ describe('CurrentRateService', () => {
|
|||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
propertyService
|
propertyService
|
||||||
);
|
);
|
||||||
exchangeRateDataService = new ExchangeRateDataService(
|
exchangeRateDataService = new ExchangeRateDataService(
|
||||||
|
@ -102,7 +102,7 @@ export class DataGatheringService {
|
|||||||
symbol
|
symbol
|
||||||
},
|
},
|
||||||
update: { marketPrice },
|
update: { marketPrice },
|
||||||
where: { date_symbol: { date, symbol } }
|
where: { dataSource_date_symbol: { dataSource, date, symbol } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -7,20 +7,22 @@ import { GoogleSheetsService } from '@ghostfolio/api/services/data-provider/goog
|
|||||||
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
|
import { ManualService } from '@ghostfolio/api/services/data-provider/manual/manual.service';
|
||||||
import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service';
|
import { RapidApiService } from '@ghostfolio/api/services/data-provider/rapid-api/rapid-api.service';
|
||||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
|
import { MarketDataModule } from '@ghostfolio/api/services/market-data.module';
|
||||||
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
|
||||||
|
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
|
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { DataEnhancerModule } from './data-enhancer/data-enhancer.module';
|
import { DataEnhancerModule } from './data-enhancer/data-enhancer.module';
|
||||||
import { YahooFinanceDataEnhancerService } from './data-enhancer/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceDataEnhancerService } from './data-enhancer/yahoo-finance/yahoo-finance.service';
|
||||||
import { DataProviderService } from './data-provider.service';
|
import { DataProviderService } from './data-provider.service';
|
||||||
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
CryptocurrencyModule,
|
CryptocurrencyModule,
|
||||||
DataEnhancerModule,
|
DataEnhancerModule,
|
||||||
|
MarketDataModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
PropertyModule,
|
PropertyModule,
|
||||||
SymbolProfileModule
|
SymbolProfileModule
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
IDataProviderHistoricalResponse,
|
IDataProviderHistoricalResponse,
|
||||||
IDataProviderResponse
|
IDataProviderResponse
|
||||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||||
|
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT, getStartOfUtcDate } from '@ghostfolio/common/helper';
|
||||||
import { UserWithSettings } from '@ghostfolio/common/types';
|
import { UserWithSettings } from '@ghostfolio/common/types';
|
||||||
@ -25,6 +26,7 @@ export class DataProviderService {
|
|||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
@Inject('DataProviderInterfaces')
|
@Inject('DataProviderInterfaces')
|
||||||
private readonly dataProviderInterfaces: DataProviderInterface[],
|
private readonly dataProviderInterfaces: DataProviderInterface[],
|
||||||
|
private readonly marketDataService: MarketDataService,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
private readonly propertyService: PropertyService
|
private readonly propertyService: PropertyService
|
||||||
) {
|
) {
|
||||||
@ -276,35 +278,23 @@ export class DataProviderService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const date = getStartOfUtcDate(new Date());
|
await this.marketDataService.updateMany({
|
||||||
|
data: Object.keys(response)
|
||||||
// Upsert quotes by imitating missing upsertMany functionality
|
.filter((symbol) => {
|
||||||
// with $transaction
|
return (
|
||||||
const upsertPromises = Object.keys(response)
|
isNumber(response[symbol].marketPrice) &&
|
||||||
.filter((symbol) => {
|
response[symbol].marketPrice > 0
|
||||||
return (
|
);
|
||||||
isNumber(response[symbol].marketPrice) &&
|
})
|
||||||
response[symbol].marketPrice > 0
|
.map((symbol) => {
|
||||||
);
|
return {
|
||||||
})
|
|
||||||
.map((symbol) =>
|
|
||||||
this.prismaService.marketData.upsert({
|
|
||||||
create: {
|
|
||||||
date,
|
|
||||||
symbol,
|
symbol,
|
||||||
dataSource: response[symbol].dataSource,
|
dataSource: response[symbol].dataSource,
|
||||||
|
date: getStartOfUtcDate(new Date()),
|
||||||
marketPrice: response[symbol].marketPrice
|
marketPrice: response[symbol].marketPrice
|
||||||
},
|
};
|
||||||
update: {
|
|
||||||
marketPrice: response[symbol].marketPrice
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
date_symbol: { date, symbol }
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
});
|
||||||
|
|
||||||
await this.prismaService.$transaction(upsertPromises);
|
|
||||||
} catch {}
|
} catch {}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -102,11 +102,46 @@ export class MarketDataService {
|
|||||||
where,
|
where,
|
||||||
create: {
|
create: {
|
||||||
dataSource: data.dataSource,
|
dataSource: data.dataSource,
|
||||||
date: where.date_symbol.date,
|
date: where.dataSource_date_symbol.date,
|
||||||
marketPrice: data.marketPrice,
|
marketPrice: data.marketPrice,
|
||||||
symbol: where.date_symbol.symbol
|
symbol: where.dataSource_date_symbol.symbol
|
||||||
},
|
},
|
||||||
update: { marketPrice: data.marketPrice }
|
update: { marketPrice: data.marketPrice }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upsert market data by imitating missing upsertMany functionality
|
||||||
|
* with $transaction
|
||||||
|
*/
|
||||||
|
public async updateMany({
|
||||||
|
data
|
||||||
|
}: {
|
||||||
|
data: Prisma.MarketDataUpdateInput[];
|
||||||
|
}): Promise<MarketData[]> {
|
||||||
|
const upsertPromises = data.map(
|
||||||
|
({ dataSource, date, marketPrice, symbol }) => {
|
||||||
|
return this.prismaService.marketData.upsert({
|
||||||
|
create: {
|
||||||
|
dataSource: <DataSource>dataSource,
|
||||||
|
date: <Date>date,
|
||||||
|
marketPrice: <number>marketPrice,
|
||||||
|
symbol: <string>symbol
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
marketPrice: <number>marketPrice
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
dataSource_date_symbol: {
|
||||||
|
dataSource: <DataSource>dataSource,
|
||||||
|
date: <Date>date,
|
||||||
|
symbol: <string>symbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.prismaService.$transaction(upsertPromises);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "MarketData_date_symbol_key";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "MarketData_dataSource_date_symbol_key" ON "MarketData"("dataSource", "date", "symbol");
|
@ -66,7 +66,7 @@ model MarketData {
|
|||||||
symbol String
|
symbol String
|
||||||
marketPrice Float
|
marketPrice Float
|
||||||
|
|
||||||
@@unique([date, symbol])
|
@@unique([dataSource, date, symbol])
|
||||||
@@index([symbol])
|
@@index([symbol])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user