Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
3e3395aff9 | |||
a2687eacbc | |||
0f2c8c856c | |||
ec4dbf2a51 | |||
3d34aa5e80 | |||
c45bd70711 | |||
9f876e6020 |
12
CHANGELOG.md
12
CHANGELOG.md
@ -5,6 +5,16 @@ 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).
|
||||
|
||||
## 0.87.0 - 19.04.2021
|
||||
|
||||
### Added
|
||||
|
||||
- Added a generic scraper
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed an issue in the user table of the admin control panel with missing data
|
||||
|
||||
## 0.86.1 - 18.04.2021
|
||||
|
||||
### Added
|
||||
@ -18,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Changed the about page for the new license
|
||||
- Optimized the data management for historical data
|
||||
- Optimized the exchange rate service
|
||||
- Improved the user table in the admin control panel
|
||||
- Improved the user table of the admin control panel
|
||||
|
||||
### Fixed
|
||||
|
||||
|
12
README.md
12
README.md
@ -45,18 +45,18 @@ Ghostfolio is for you if you are...
|
||||
- ✅ Static analysis to identify potential risks in your portfolio
|
||||
- ✅ Dark Mode
|
||||
|
||||
## Technology
|
||||
## Technology Stack
|
||||
|
||||
Ghostfolio is a modern web application written in [TypeScript](https://www.typescriptlang.org) and organized as an [Nx](https://nx.dev) workspace.
|
||||
|
||||
### Frontend
|
||||
|
||||
The frontend is built with [Angular](https://angular.io).
|
||||
|
||||
### Backend
|
||||
|
||||
The backend is based on [NestJS](https://nestjs.com) using [PostgreSQL](https://www.postgresql.org) as a database and [Redis](https://redis.io) for caching.
|
||||
|
||||
### Frontend
|
||||
|
||||
The frontend is built with [Angular](https://angular.io).
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
@ -92,4 +92,6 @@ Run `yarn test`
|
||||
|
||||
## License
|
||||
|
||||
© 2021 [Ghostfolio](https://ghostfol.io)
|
||||
|
||||
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
@ -10,7 +11,6 @@ import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
import { getPermissions, hasPermission, permissions } from 'libs/helper/src';
|
||||
|
||||
import { DataGatheringService } from '../../services/data-gathering.service';
|
||||
import { AdminService } from './admin.service';
|
||||
|
@ -4,6 +4,7 @@ import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { DataGatheringService } from '../../services/data-gathering.service';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { AlphaVantageService } from '../../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '../../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '../../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '../../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
@ -21,6 +22,7 @@ import { AdminService } from './admin.service';
|
||||
DataGatheringService,
|
||||
DataProviderService,
|
||||
ExchangeRateDataService,
|
||||
GhostfolioScraperApiService,
|
||||
PrismaService,
|
||||
RakutenRapidApiService,
|
||||
YahooFinanceService
|
||||
|
@ -10,6 +10,7 @@ import { CronService } from '../services/cron.service';
|
||||
import { DataGatheringService } from '../services/data-gathering.service';
|
||||
import { DataProviderService } from '../services/data-provider.service';
|
||||
import { AlphaVantageService } from '../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
||||
@ -65,6 +66,7 @@ import { UserModule } from './user/user.module';
|
||||
DataGatheringService,
|
||||
DataProviderService,
|
||||
ExchangeRateDataService,
|
||||
GhostfolioScraperApiService,
|
||||
PrismaService,
|
||||
RakutenRapidApiService,
|
||||
YahooFinanceService
|
||||
|
@ -1,3 +1,8 @@
|
||||
import {
|
||||
baseCurrency,
|
||||
benchmarks,
|
||||
isApiTokenAuthorized
|
||||
} from '@ghostfolio/helper';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
@ -12,8 +17,6 @@ import { REQUEST } from '@nestjs/core';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
import { parse } from 'date-fns';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
import { baseCurrency, benchmarks } from 'libs/helper/src';
|
||||
import { isApiTokenAuthorized } from 'libs/helper/src';
|
||||
|
||||
import { CreateOrderDto } from './create-order.dto';
|
||||
import { ExperimentalService } from './experimental.service';
|
||||
|
@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { AlphaVantageService } from '../../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '../../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '../../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '../../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
@ -20,6 +21,7 @@ import { ExperimentalService } from './experimental.service';
|
||||
DataProviderService,
|
||||
ExchangeRateDataService,
|
||||
ExperimentalService,
|
||||
GhostfolioScraperApiService,
|
||||
PrismaService,
|
||||
RakutenRapidApiService,
|
||||
RulesService,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
@ -17,7 +18,6 @@ import { Order as OrderModel } from '@prisma/client';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
import { parseISO } from 'date-fns';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
import { getPermissions, hasPermission, permissions } from 'libs/helper/src';
|
||||
|
||||
import { nullifyValuesInObjects } from '../../helper/object.helper';
|
||||
import { ImpersonationService } from '../../services/impersonation.service';
|
||||
|
@ -4,6 +4,7 @@ import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { DataGatheringService } from '../../services/data-gathering.service';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { AlphaVantageService } from '../../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '../../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '../../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '../../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ImpersonationService } from '../../services/impersonation.service';
|
||||
@ -22,6 +23,7 @@ import { OrderService } from './order.service';
|
||||
ConfigurationService,
|
||||
DataGatheringService,
|
||||
DataProviderService,
|
||||
GhostfolioScraperApiService,
|
||||
ImpersonationService,
|
||||
OrderService,
|
||||
PrismaService,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
@ -13,7 +14,6 @@ import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { Response } from 'express';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
import { getPermissions, hasPermission, permissions } from 'libs/helper/src';
|
||||
|
||||
import {
|
||||
hasNotDefinedValuesInObject,
|
||||
|
@ -4,6 +4,7 @@ import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { DataGatheringService } from '../../services/data-gathering.service';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { AlphaVantageService } from '../../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '../../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '../../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '../../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
@ -27,6 +28,7 @@ import { PortfolioService } from './portfolio.service';
|
||||
DataGatheringService,
|
||||
DataProviderService,
|
||||
ExchangeRateDataService,
|
||||
GhostfolioScraperApiService,
|
||||
ImpersonationService,
|
||||
OrderService,
|
||||
PortfolioService,
|
||||
|
@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { AlphaVantageService } from '../../services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '../../services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '../../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '../../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
@ -16,6 +17,7 @@ import { SymbolService } from './symbol.service';
|
||||
AlphaVantageService,
|
||||
ConfigurationService,
|
||||
DataProviderService,
|
||||
GhostfolioScraperApiService,
|
||||
PrismaService,
|
||||
RakutenRapidApiService,
|
||||
SymbolService,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
@ -15,7 +16,6 @@ import { AuthGuard } from '@nestjs/passport';
|
||||
import { Provider } from '@prisma/client';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
import { getPermissions, hasPermission, permissions } from 'libs/helper/src';
|
||||
|
||||
import { UserItem } from './interfaces/user-item.interface';
|
||||
import { User } from './interfaces/user.interface';
|
||||
|
@ -1,8 +1,12 @@
|
||||
import {
|
||||
getPermissions,
|
||||
locale,
|
||||
permissions,
|
||||
resetHours
|
||||
} from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Currency, Prisma, Provider, User } from '@prisma/client';
|
||||
import { add } from 'date-fns';
|
||||
import { locale, permissions, resetHours } from 'libs/helper/src';
|
||||
import { getPermissions } from 'libs/helper/src';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { baseCurrency, getUtc, getYesterday } from '@ghostfolio/helper';
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { Currency, Role, Type } from '@prisma/client';
|
||||
import { baseCurrency } from 'libs/helper/src';
|
||||
import { getYesterday } from 'libs/helper/src';
|
||||
import { getUtc } from 'libs/helper/src';
|
||||
|
||||
import { ConfigurationService } from '../services/configuration.service';
|
||||
import { DataProviderService } from '../services/data-provider.service';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { getToday, getYesterday, resetHours } from '@ghostfolio/helper';
|
||||
import {
|
||||
PortfolioItem,
|
||||
Position
|
||||
@ -18,7 +19,6 @@ import {
|
||||
setMonth,
|
||||
sub
|
||||
} from 'date-fns';
|
||||
import { getToday, getYesterday, resetHours } from 'libs/helper/src';
|
||||
import { cloneDeep, isEmpty } from 'lodash';
|
||||
import * as roundTo from 'round-to';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { groupBy } from '@ghostfolio/helper';
|
||||
import { Currency } from '@prisma/client';
|
||||
import { groupBy } from 'libs/helper/src';
|
||||
|
||||
import { PortfolioPosition } from '../app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
||||
|
@ -12,6 +12,7 @@ export class ConfigurationService {
|
||||
ACCESS_TOKEN_SALT: str(),
|
||||
ALPHA_VANTAGE_API_KEY: str({ default: '' }),
|
||||
CACHE_TTL: num({ default: 1 }),
|
||||
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
|
||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
||||
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
||||
GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }),
|
||||
|
@ -1,3 +1,9 @@
|
||||
import {
|
||||
benchmarks,
|
||||
currencyPairs,
|
||||
getUtc,
|
||||
resetHours
|
||||
} from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import {
|
||||
differenceInHours,
|
||||
@ -8,8 +14,6 @@ import {
|
||||
isBefore,
|
||||
subDays
|
||||
} from 'date-fns';
|
||||
import { benchmarks, currencyPairs } from 'libs/helper/src';
|
||||
import { getUtc, resetHours } from 'libs/helper/src';
|
||||
|
||||
import { ConfigurationService } from './configuration.service';
|
||||
import { DataProviderService } from './data-provider.service';
|
||||
@ -196,10 +200,36 @@ export class DataGatheringService {
|
||||
return benchmarksToGather;
|
||||
}
|
||||
|
||||
private async getCustomSymbolsToGather(startDate: Date) {
|
||||
const customSymbolsToGather = [];
|
||||
|
||||
if (this.configurationService.get('ENABLE_FEATURE_CUSTOM_SYMBOLS')) {
|
||||
try {
|
||||
const {
|
||||
value: scraperConfigString
|
||||
} = await this.prisma.property.findFirst({
|
||||
select: {
|
||||
value: true
|
||||
},
|
||||
where: { key: 'SCRAPER_CONFIG' }
|
||||
});
|
||||
|
||||
JSON.parse(scraperConfigString).forEach((item) => {
|
||||
customSymbolsToGather.push({
|
||||
date: startDate,
|
||||
symbol: item.symbol
|
||||
});
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
return customSymbolsToGather;
|
||||
}
|
||||
|
||||
private async getSymbols7D(): Promise<{ date: Date; symbol: string }[]> {
|
||||
const startDate = subDays(resetHours(new Date()), 7);
|
||||
|
||||
let distinctOrders = await this.prisma.order.findMany({
|
||||
const distinctOrders = await this.prisma.order.findMany({
|
||||
distinct: ['symbol'],
|
||||
orderBy: [{ symbol: 'asc' }],
|
||||
select: { symbol: true }
|
||||
@ -219,8 +249,13 @@ export class DataGatheringService {
|
||||
};
|
||||
});
|
||||
|
||||
const customSymbolsToGather = await this.getCustomSymbolsToGather(
|
||||
startDate
|
||||
);
|
||||
|
||||
return [
|
||||
...this.getBenchmarksToGather(startDate),
|
||||
...customSymbolsToGather,
|
||||
...currencyPairsToGather,
|
||||
...distinctOrdersWithDate
|
||||
];
|
||||
@ -229,11 +264,9 @@ export class DataGatheringService {
|
||||
private async getSymbolsMax() {
|
||||
const startDate = new Date(getUtc('2000-01-01'));
|
||||
|
||||
let distinctOrders = await this.prisma.order.findMany({
|
||||
distinct: ['symbol'],
|
||||
orderBy: [{ date: 'asc' }],
|
||||
select: { date: true, symbol: true }
|
||||
});
|
||||
const customSymbolsToGather = await this.getCustomSymbolsToGather(
|
||||
startDate
|
||||
);
|
||||
|
||||
const currencyPairsToGather = currencyPairs.map((symbol) => {
|
||||
return {
|
||||
@ -242,8 +275,15 @@ export class DataGatheringService {
|
||||
};
|
||||
});
|
||||
|
||||
const distinctOrders = await this.prisma.order.findMany({
|
||||
distinct: ['symbol'],
|
||||
orderBy: [{ date: 'asc' }],
|
||||
select: { date: true, symbol: true }
|
||||
});
|
||||
|
||||
return [
|
||||
...this.getBenchmarksToGather(startDate),
|
||||
...customSymbolsToGather,
|
||||
...currencyPairsToGather,
|
||||
...distinctOrders
|
||||
];
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { isCrypto, isRakutenRapidApi } from '@ghostfolio/helper';
|
||||
import {
|
||||
isCrypto,
|
||||
isGhostfolioScraperApi,
|
||||
isRakutenRapidApi
|
||||
} from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { MarketData } from '@prisma/client';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import { ConfigurationService } from './configuration.service';
|
||||
import { AlphaVantageService } from './data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from './data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from './data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from './data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { DataProviderInterface } from './interfaces/data-provider.interface';
|
||||
@ -20,6 +25,7 @@ export class DataProviderService implements DataProviderInterface {
|
||||
public constructor(
|
||||
private readonly alphaVantageService: AlphaVantageService,
|
||||
private readonly configurationService: ConfigurationService,
|
||||
private readonly ghostfolioScraperApiService: GhostfolioScraperApiService,
|
||||
private prisma: PrismaService,
|
||||
private readonly rakutenRapidApiService: RakutenRapidApiService,
|
||||
private readonly yahooFinanceService: YahooFinanceService
|
||||
@ -33,7 +39,9 @@ export class DataProviderService implements DataProviderInterface {
|
||||
if (aSymbols.length === 1) {
|
||||
const symbol = aSymbols[0];
|
||||
|
||||
if (isRakutenRapidApi(symbol)) {
|
||||
if (isGhostfolioScraperApi(symbol)) {
|
||||
return this.ghostfolioScraperApiService.get(aSymbols);
|
||||
} else if (isRakutenRapidApi(symbol)) {
|
||||
return this.rakutenRapidApiService.get(aSymbols);
|
||||
}
|
||||
}
|
||||
@ -53,12 +61,12 @@ export class DataProviderService implements DataProviderInterface {
|
||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||
} = {};
|
||||
|
||||
let granularityQuery =
|
||||
const granularityQuery =
|
||||
aGranularity === 'month'
|
||||
? `AND (date_part('day', date) = 1 OR date >= TIMESTAMP 'yesterday')`
|
||||
: '';
|
||||
|
||||
let rangeQuery =
|
||||
const rangeQuery =
|
||||
from && to
|
||||
? `AND date >= '${format(from, 'yyyy-MM-dd')}' AND date <= '${format(
|
||||
to,
|
||||
@ -127,6 +135,15 @@ export class DataProviderService implements DataProviderInterface {
|
||||
...dataOfAlphaVantage[symbol]
|
||||
}
|
||||
};
|
||||
} else if (isGhostfolioScraperApi(symbol)) {
|
||||
const dataOfGhostfolioScraperApi = await this.ghostfolioScraperApiService.getHistorical(
|
||||
[symbol],
|
||||
undefined,
|
||||
from,
|
||||
to
|
||||
);
|
||||
|
||||
return dataOfGhostfolioScraperApi;
|
||||
} else if (
|
||||
isRakutenRapidApi(symbol) &&
|
||||
this.configurationService.get('RAKUTEN_RAPID_API_KEY')
|
||||
|
@ -0,0 +1,103 @@
|
||||
import { getYesterday } from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as bent from 'bent';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
||||
import { Granularity } from '../../interfaces/granularity.type';
|
||||
import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse
|
||||
} from '../../interfaces/interfaces';
|
||||
import { PrismaService } from '../../prisma.service';
|
||||
import { Currency } from '.prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
public constructor(private prisma: PrismaService) {}
|
||||
|
||||
public async get(
|
||||
aSymbols: string[]
|
||||
): Promise<{ [symbol: string]: IDataProviderResponse }> {
|
||||
if (aSymbols.length <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const symbol = aSymbols[0];
|
||||
const { marketPrice } = await this.prisma.marketData.findFirst({
|
||||
orderBy: {
|
||||
date: 'desc'
|
||||
},
|
||||
where: {
|
||||
symbol
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
[symbol]: {
|
||||
marketPrice,
|
||||
currency: Currency.CHF,
|
||||
isMarketOpen: true,
|
||||
name: symbol
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
public async getHistorical(
|
||||
aSymbols: string[],
|
||||
aGranularity: Granularity = 'day',
|
||||
from: Date,
|
||||
to: Date
|
||||
): Promise<{
|
||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||
}> {
|
||||
if (aSymbols.length <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
const symbol = aSymbols[0];
|
||||
|
||||
const {
|
||||
value: scraperConfigString
|
||||
} = await this.prisma.property.findFirst({
|
||||
select: {
|
||||
value: true
|
||||
},
|
||||
where: { key: 'SCRAPER_CONFIG' }
|
||||
});
|
||||
|
||||
const scraperConfig = JSON.parse(scraperConfigString).find((item) => {
|
||||
return item.symbol === symbol;
|
||||
});
|
||||
|
||||
const get = bent(scraperConfig.url, 'GET', 'string', 200, {});
|
||||
|
||||
const html = await get();
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const string = $(scraperConfig.selector).text().replace('CHF', '').trim();
|
||||
|
||||
const value = parseFloat(string);
|
||||
|
||||
return {
|
||||
[symbol]: {
|
||||
[format(getYesterday(), 'yyyy-MM-dd')]: {
|
||||
marketPrice: value
|
||||
}
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { getToday, getYesterday } from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as bent from 'bent';
|
||||
import { format, subMonths, subWeeks, subYears } from 'date-fns';
|
||||
import { getToday, getYesterday } from 'libs/helper/src';
|
||||
|
||||
import { ConfigurationService } from '../../configuration.service';
|
||||
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { isCrypto, isCurrency, parseCurrency } from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { format } from 'date-fns';
|
||||
import { isCrypto, isCurrency, parseCurrency } from 'libs/helper/src';
|
||||
import * as yahooFinance from 'yahoo-finance';
|
||||
|
||||
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
||||
|
@ -58,8 +58,8 @@ export class ExchangeRateDataService {
|
||||
if (!this.currencies[pair]) {
|
||||
// Not found, calculate indirectly via USD
|
||||
this.currencies[pair] =
|
||||
resultExtended[`${currency1}${Currency.USD}`][date].marketPrice *
|
||||
resultExtended[`${Currency.USD}${currency2}`][date].marketPrice;
|
||||
resultExtended[`${currency1}${Currency.USD}`]?.[date]?.marketPrice *
|
||||
resultExtended[`${Currency.USD}${currency2}`]?.[date]?.marketPrice;
|
||||
|
||||
// Calculate the opposite direction
|
||||
this.currencies[`${currency2}${currency1}`] = 1 / this.currencies[pair];
|
||||
|
@ -4,6 +4,7 @@ export interface Environment extends CleanedEnvAccessors {
|
||||
ACCESS_TOKEN_SALT: string;
|
||||
ALPHA_VANTAGE_API_KEY: string;
|
||||
CACHE_TTL: number;
|
||||
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
|
||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
||||
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
||||
GOOGLE_CLIENT_ID: string;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
DEFAULT_DATE_FORMAT,
|
||||
DEFAULT_DATE_FORMAT_MONTH_YEAR
|
||||
} from 'libs/helper/src';
|
||||
} from '@ghostfolio/helper';
|
||||
|
||||
export const DateFormats = {
|
||||
display: {
|
||||
|
@ -6,11 +6,15 @@ import {
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import {
|
||||
hasPermission,
|
||||
permissions,
|
||||
primaryColorHex,
|
||||
secondaryColorHex
|
||||
} from '@ghostfolio/helper';
|
||||
import { MaterialCssVarsService } from 'angular-material-css-vars';
|
||||
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { primaryColorHex, secondaryColorHex } from 'libs/helper/src';
|
||||
import { hasPermission, permissions } from 'libs/helper/src';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
OnChanges,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { resolveFearAndGreedIndex } from 'libs/helper/src';
|
||||
import { resolveFearAndGreedIndex } from '@ghostfolio/helper';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-fear-and-greed-index',
|
||||
|
@ -6,9 +6,9 @@ import {
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { hasPermission, permissions } from 'libs/helper/src';
|
||||
import { EMPTY, Subject } from 'rxjs';
|
||||
import { catchError, takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { primaryColorRgb } from '@ghostfolio/helper';
|
||||
import { PortfolioItem } from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import {
|
||||
LineController,
|
||||
@ -18,7 +19,6 @@ import {
|
||||
TimeScale
|
||||
} from 'chart.js';
|
||||
import { Chart } from 'chart.js';
|
||||
import { primaryColorRgb } from 'libs/helper/src';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-investment-chart',
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/helper';
|
||||
import {
|
||||
Chart,
|
||||
Filler,
|
||||
@ -18,7 +19,6 @@ import {
|
||||
PointElement,
|
||||
TimeScale
|
||||
} from 'chart.js';
|
||||
import { primaryColorRgb, secondaryColorRgb } from 'libs/helper/src';
|
||||
|
||||
import { LineChartItem } from './interfaces/line-chart.interface';
|
||||
|
||||
|
@ -5,13 +5,10 @@ import {
|
||||
Component,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
ViewChild
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { PortfolioItem } from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import { Chart } from 'chart.js';
|
||||
import { endOfDay, parseISO, startOfDay } from 'date-fns';
|
||||
import { primaryColorRgb } from 'libs/helper/src';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-portfolio-positions-chart',
|
||||
|
@ -13,7 +13,7 @@ import { PositionDetailDialogParams } from './interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
host: { class: 'd-flex flex-column h-100' },
|
||||
selector: 'position-detail-dialog',
|
||||
selector: 'gf-position-detail-dialog',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
templateUrl: 'position-detail-dialog.html',
|
||||
styleUrls: ['./position-detail-dialog.component.scss']
|
||||
|
@ -26,4 +26,4 @@ import { PositionDetailDialog } from './position-detail-dialog.component';
|
||||
providers: [],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class PositionDetailDialogModule {}
|
||||
export class GfPositionDetailDialogModule {}
|
||||
|
@ -7,7 +7,7 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
import { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
||||
import { GfTrendIndicatorModule } from '../trend-indicator/trend-indicator.module';
|
||||
import { GfValueModule } from '../value/value.module';
|
||||
import { PositionDetailDialogModule } from './position-detail-dialog/position-detail-dialog.module';
|
||||
import { GfPositionDetailDialogModule } from './position-detail-dialog/position-detail-dialog.module';
|
||||
import { PositionComponent } from './position.component';
|
||||
|
||||
@NgModule({
|
||||
@ -15,12 +15,12 @@ import { PositionComponent } from './position.component';
|
||||
exports: [PositionComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfPositionDetailDialogModule,
|
||||
GfSymbolIconModule,
|
||||
GfTrendIndicatorModule,
|
||||
GfValueModule,
|
||||
MatDialogModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
PositionDetailDialogModule,
|
||||
RouterModule
|
||||
],
|
||||
providers: [],
|
||||
|
@ -10,7 +10,7 @@ import { RouterModule } from '@angular/router';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { GfNoTransactionsInfoModule } from '../no-transactions-info/no-transactions-info.module';
|
||||
import { PositionDetailDialogModule } from '../position/position-detail-dialog/position-detail-dialog.module';
|
||||
import { GfPositionDetailDialogModule } from '../position/position-detail-dialog/position-detail-dialog.module';
|
||||
import { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
||||
import { GfValueModule } from '../value/value.module';
|
||||
import { PositionsTableComponent } from './positions-table.component';
|
||||
@ -21,6 +21,7 @@ import { PositionsTableComponent } from './positions-table.component';
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfNoTransactionsInfoModule,
|
||||
GfPositionDetailDialogModule,
|
||||
GfSymbolIconModule,
|
||||
GfValueModule,
|
||||
MatButtonModule,
|
||||
@ -30,7 +31,6 @@ import { PositionsTableComponent } from './positions-table.component';
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
PositionDetailDialogModule,
|
||||
RouterModule
|
||||
],
|
||||
providers: [],
|
||||
|
@ -13,8 +13,8 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||
import { Order as OrderModel } from '@prisma/client';
|
||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -8,7 +8,7 @@ import { MatTableModule } from '@angular/material/table';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { PositionDetailDialogModule } from '../position/position-detail-dialog/position-detail-dialog.module';
|
||||
import { GfPositionDetailDialogModule } from '../position/position-detail-dialog/position-detail-dialog.module';
|
||||
import { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
||||
import { GfValueModule } from '../value/value.module';
|
||||
import { TransactionsTableComponent } from './transactions-table.component';
|
||||
@ -18,6 +18,7 @@ import { TransactionsTableComponent } from './transactions-table.component';
|
||||
exports: [TransactionsTableComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfPositionDetailDialogModule,
|
||||
GfSymbolIconModule,
|
||||
GfValueModule,
|
||||
MatButtonModule,
|
||||
@ -26,7 +27,6 @@ import { TransactionsTableComponent } from './transactions-table.component';
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
NgxSkeletonLoaderModule,
|
||||
PositionDetailDialogModule,
|
||||
RouterModule
|
||||
],
|
||||
providers: [],
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
OnChanges,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||
import { format, isDate } from 'date-fns';
|
||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
||||
import { isNumber } from 'lodash';
|
||||
|
||||
@Component({
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { baseCurrency } from '@ghostfolio/helper';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { environment } from 'apps/client/src/environments/environment';
|
||||
import { baseCurrency } from 'libs/helper/src';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
<a href="https://dotsilver.ch">Thomas Kaul</a>.
|
||||
<ng-container *ngIf="lastPublish">
|
||||
This instance has been last published on {{ lastPublish
|
||||
}}</ng-container
|
||||
}}.</ng-container
|
||||
>
|
||||
</p>
|
||||
<p>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||
import { Access } from 'apps/api/src/app/access/interfaces/access.interface';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||
import { AdminData } from 'apps/api/src/app/admin/interfaces/admin-data.interface';
|
||||
import { formatDistanceToNow, isValid, parseISO, sub } from 'date-fns';
|
||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -97,16 +97,20 @@ export class AdminPageComponent implements OnInit {
|
||||
}
|
||||
|
||||
public formatDistanceToNow(aDateString: string) {
|
||||
const distanceString = formatDistanceToNow(
|
||||
sub(parseISO(aDateString), { seconds: 10 }),
|
||||
{
|
||||
addSuffix: true
|
||||
}
|
||||
);
|
||||
if (aDateString) {
|
||||
const distanceString = formatDistanceToNow(
|
||||
sub(parseISO(aDateString), { seconds: 10 }),
|
||||
{
|
||||
addSuffix: true
|
||||
}
|
||||
);
|
||||
|
||||
return distanceString === 'less than a minute ago'
|
||||
? 'just now'
|
||||
: distanceString;
|
||||
return distanceString === 'less than a minute ago'
|
||||
? 'just now'
|
||||
: distanceString;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
|
@ -99,13 +99,13 @@
|
||||
{{ userItem.createdAt | date: defaultDateFormat }}
|
||||
</td>
|
||||
<td class="mat-cell pr-2 py-2 text-truncate">
|
||||
{{ userItem._count.Order }}
|
||||
{{ userItem._count?.Order }}
|
||||
</td>
|
||||
<td class="mat-cell pr-2 py-2 text-truncate">
|
||||
{{ userItem.Analytics.activityCount }}
|
||||
{{ userItem.Analytics?.activityCount }}
|
||||
</td>
|
||||
<td class="mat-cell pr-2 py-2 text-truncate">
|
||||
{{ formatDistanceToNow(userItem.Analytics.updatedAt) }}
|
||||
{{ formatDistanceToNow(userItem.Analytics?.updatedAt) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import { DateRange } from 'apps/api/src/app/portfolio/interfaces/date-range.type';
|
||||
import { PortfolioOverview } from 'apps/api/src/app/portfolio/interfaces/portfolio-overview.interface';
|
||||
import { PortfolioPerformance } from 'apps/api/src/app/portfolio/interfaces/portfolio-performance.interface';
|
||||
@ -10,7 +11,6 @@ import {
|
||||
RANGE,
|
||||
SettingsStorageService
|
||||
} from 'apps/client/src/app/services/settings-storage.service';
|
||||
import { hasPermission, permissions } from 'libs/helper/src';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import { Order as OrderModel } from '@prisma/client';
|
||||
import { UpdateOrderDto } from 'apps/api/src/app/order/update-order.dto';
|
||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
||||
import { hasPermission, permissions } from 'libs/helper/src';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
@ -66,6 +66,10 @@ export function isCurrency(aSymbol = '') {
|
||||
);
|
||||
}
|
||||
|
||||
export function isGhostfolioScraperApi(aSymbol = '') {
|
||||
return aSymbol.startsWith('[GF]');
|
||||
}
|
||||
|
||||
export function isRakutenRapidApi(aSymbol = '') {
|
||||
return aSymbol === 'GF.FEAR_AND_GREED_INDEX';
|
||||
}
|
||||
|
13
package.json
13
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "0.86.1",
|
||||
"version": "0.87.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@ -13,9 +13,9 @@
|
||||
"affected:lint": "nx affected:lint",
|
||||
"affected:test": "nx affected:test",
|
||||
"angular": "node --max_old_space_size=32768 ./node_modules/@angular/cli/bin/ng",
|
||||
"build:all": "ng build --prod api && ng build --prod client && yarn run replace-placeholders-in-build",
|
||||
"build:all": "ng build --prod api && ng build --prod client && yarn replace-placeholders-in-build",
|
||||
"clean": "rimraf dist",
|
||||
"database:push": "yarn prisma db push --preview-feature",
|
||||
"database:push": "prisma db push --preview-feature",
|
||||
"database:seed": "prisma db seed --preview-feature",
|
||||
"dep-graph": "nx dep-graph",
|
||||
"e2e": "ng e2e",
|
||||
@ -27,17 +27,17 @@
|
||||
"lint": "nx workspace-lint && ng lint",
|
||||
"ng": "nx",
|
||||
"nx": "nx",
|
||||
"postinstall": "yarn prisma generate",
|
||||
"postinstall": "prisma generate",
|
||||
"replace-placeholders-in-build": "node ./replace.build.js",
|
||||
"setup:database": "yarn database:push && yarn database:seed",
|
||||
"start": "node dist/apps/api/main",
|
||||
"start:client": "ng serve client --hmr -o",
|
||||
"start:prod": "node apps/api/main",
|
||||
"start:server": "yarn run nx serve api",
|
||||
"start:server": "nx serve api",
|
||||
"test": "ng test",
|
||||
"ts-node": "ts-node --compiler-options '{\"module\":\"CommonJS\"}'",
|
||||
"update": "nx migrate latest",
|
||||
"watch:server": "yarn run nx build api --watch",
|
||||
"watch:server": "nx build api --watch",
|
||||
"workspace-generator": "nx workspace-generator"
|
||||
},
|
||||
"dependencies": {
|
||||
@ -71,6 +71,7 @@
|
||||
"chart.js": "3.0.2",
|
||||
"chartjs-adapter-date-fns": "1.1.0-beta.1",
|
||||
"chartjs-chart-timeline": "0.4.0",
|
||||
"cheerio": "1.0.0-rc.6",
|
||||
"class-transformer": "0.3.2",
|
||||
"class-validator": "0.13.1",
|
||||
"countup.js": "2.0.7",
|
||||
|
164
prisma/seed.ts
164
prisma/seed.ts
@ -2,7 +2,77 @@ import { Currency, PrismaClient, Role, Type } from '@prisma/client';
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const admin = await prisma.user.upsert({
|
||||
const platformBitcoinSuisse = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: '70b6e475-a2b9-4527-99db-943e4f38ce45',
|
||||
name: 'Bitcoin Suisse',
|
||||
url: 'https://www.bitcoinsuisse.com'
|
||||
},
|
||||
update: {},
|
||||
where: { id: '70b6e475-a2b9-4527-99db-943e4f38ce45' }
|
||||
});
|
||||
|
||||
const platformBitpanda = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: 'debf9110-498f-4811-b972-7ebbd317e730',
|
||||
name: 'Bitpanda',
|
||||
url: 'https://www.bitpanda.com'
|
||||
},
|
||||
update: {},
|
||||
where: { id: 'debf9110-498f-4811-b972-7ebbd317e730' }
|
||||
});
|
||||
|
||||
const platformCoinbase = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: '8dc24b88-bb92-4152-af25-fe6a31643e26',
|
||||
name: 'Coinbase',
|
||||
url: 'https://www.coinbase.com'
|
||||
},
|
||||
update: {},
|
||||
where: { id: '8dc24b88-bb92-4152-af25-fe6a31643e26' }
|
||||
});
|
||||
|
||||
const platformDegiro = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: '94c1a2f4-a666-47be-84cd-4c8952e74c81',
|
||||
name: 'DEGIRO',
|
||||
url: 'https://www.degiro.eu'
|
||||
},
|
||||
update: {},
|
||||
where: { id: '94c1a2f4-a666-47be-84cd-4c8952e74c81' }
|
||||
});
|
||||
|
||||
const platformInteractiveBrokers = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: '9da3a8a7-4795-43e3-a6db-ccb914189737',
|
||||
name: 'Interactive Brokers',
|
||||
url: 'https://www.interactivebrokers.com'
|
||||
},
|
||||
update: {},
|
||||
where: { id: '9da3a8a7-4795-43e3-a6db-ccb914189737' }
|
||||
});
|
||||
|
||||
const platformPostFinance = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: '5377d9df-0d25-42c2-9d9b-e4c63166281e',
|
||||
name: 'PostFinance',
|
||||
url: 'https://www.postfinance.ch'
|
||||
},
|
||||
update: {},
|
||||
where: { id: '5377d9df-0d25-42c2-9d9b-e4c63166281e' }
|
||||
});
|
||||
|
||||
const platformSwissquote = await prisma.platform.upsert({
|
||||
create: {
|
||||
id: '1377d9df-0d25-42c2-9d9b-e4c63156291f',
|
||||
name: 'Swissquote',
|
||||
url: 'https://swissquote.com'
|
||||
},
|
||||
update: {},
|
||||
where: { id: '1377d9df-0d25-42c2-9d9b-e4c63156291f' }
|
||||
});
|
||||
|
||||
const userAdmin = await prisma.user.upsert({
|
||||
create: {
|
||||
accessToken:
|
||||
'c689bcc894e4a420cb609ee34271f3e07f200594f7d199c50d75add7102889eb60061a04cd2792ebc853c54e37308271271e7bf588657c9e0c37faacbc28c3c6',
|
||||
@ -14,7 +84,7 @@ async function main() {
|
||||
where: { id: '4e1af723-95f6-44f8-92a7-464df17f6ec3' }
|
||||
});
|
||||
|
||||
const demo = await prisma.user.upsert({
|
||||
const userDemo = await prisma.user.upsert({
|
||||
create: {
|
||||
accessToken:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjliMTEyYjRkLTNiN2QtNGJhZC05YmRkLTNiMGY3YjRkYWMyZiIsImlhdCI6MTYxODUxMjAxNCwiZXhwIjoxNjIxMTA0MDE0fQ.l3WUxpI0hxuQtdPrD0kd7sem6S2kx_7CrdNvkmlKuWw',
|
||||
@ -28,10 +98,88 @@ async function main() {
|
||||
date: new Date(Date.UTC(2017, 0, 3, 0, 0, 0)),
|
||||
fee: 30,
|
||||
id: 'cf7c0418-8535-4089-ae3d-5dbfa0aec2e1',
|
||||
platformId: platformDegiro.id,
|
||||
quantity: 50,
|
||||
symbol: 'TSLA',
|
||||
type: Type.BUY,
|
||||
unitPrice: 42.97
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2017, 7, 16, 0, 0, 0)),
|
||||
fee: 29.9,
|
||||
id: 'a1c5d73a-8631-44e5-ac44-356827a5212c',
|
||||
platformId: platformCoinbase.id,
|
||||
quantity: 0.5614682,
|
||||
symbol: 'BTCUSD',
|
||||
type: Type.BUY,
|
||||
unitPrice: 3562.089535970158
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2018, 9, 1, 0, 0, 0)),
|
||||
fee: 80.79,
|
||||
id: '71c08e2a-4a86-44ae-a890-c337de5d5f9b',
|
||||
platformId: platformInteractiveBrokers.id,
|
||||
quantity: 5,
|
||||
symbol: 'AMZN',
|
||||
type: Type.BUY,
|
||||
unitPrice: 2021.99
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2019, 2, 1, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '385f2c2c-d53e-4937-b0e5-e92ef6020d4e',
|
||||
platformId: platformInteractiveBrokers.id,
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
type: Type.BUY,
|
||||
unitPrice: 144.38
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2019, 8, 3, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '185f2c2c-d53e-4937-b0e5-a93ef6020d4e',
|
||||
platformId: platformInteractiveBrokers.id,
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
type: Type.BUY,
|
||||
unitPrice: 147.99
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2020, 2, 2, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '347b0430-a84f-4031-a0f9-390399066ad6',
|
||||
platformId: platformInteractiveBrokers.id,
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
type: Type.BUY,
|
||||
unitPrice: 151.41
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2020, 8, 1, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: '67ec3f47-3189-4b63-ba05-60d3a06b302f',
|
||||
platformId: platformInteractiveBrokers.id,
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
type: Type.BUY,
|
||||
unitPrice: 177.69
|
||||
},
|
||||
{
|
||||
currency: Currency.USD,
|
||||
date: new Date(Date.UTC(2020, 2, 1, 0, 0, 0)),
|
||||
fee: 19.9,
|
||||
id: 'd01c6fbc-fa8d-47e6-8e80-66f882d2bfd2',
|
||||
platformId: platformInteractiveBrokers.id,
|
||||
quantity: 10,
|
||||
symbol: 'VTI',
|
||||
type: Type.BUY,
|
||||
unitPrice: 203.15
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -40,7 +188,17 @@ async function main() {
|
||||
where: { id: '9b112b4d-3b7d-4bad-9bdd-3b0f7b4dac2f' }
|
||||
});
|
||||
|
||||
console.log({ admin, demo });
|
||||
console.log({
|
||||
platformBitcoinSuisse,
|
||||
platformBitpanda,
|
||||
platformCoinbase,
|
||||
platformDegiro,
|
||||
platformInteractiveBrokers,
|
||||
platformPostFinance,
|
||||
platformSwissquote,
|
||||
userAdmin,
|
||||
userDemo
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
|
83
yarn.lock
83
yarn.lock
@ -4042,6 +4042,29 @@ check-more-types@^2.24.0:
|
||||
resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600"
|
||||
integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=
|
||||
|
||||
cheerio-select@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/cheerio-select/-/cheerio-select-1.4.0.tgz#3a16f21e37a2ef0f211d6d1aa4eff054bb22cdc9"
|
||||
integrity sha512-sobR3Yqz27L553Qa7cK6rtJlMDbiKPdNywtR95Sj/YgfpLfy0u6CGJuaBKe5YE/vTc23SCRKxWSdlon/w6I/Ew==
|
||||
dependencies:
|
||||
css-select "^4.1.2"
|
||||
css-what "^5.0.0"
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
domutils "^2.6.0"
|
||||
|
||||
cheerio@1.0.0-rc.6:
|
||||
version "1.0.0-rc.6"
|
||||
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.6.tgz#a5ae81ab483aeefa1280c325543c601145506240"
|
||||
integrity sha512-hjx1XE1M/D5pAtMgvWwE21QClmAEeGHOIDfycgmndisdNgI6PE1cGRQkMGBcsbUbmEQyWu5PJLUcAOjtQS8DWw==
|
||||
dependencies:
|
||||
cheerio-select "^1.3.0"
|
||||
dom-serializer "^1.3.1"
|
||||
domhandler "^4.1.0"
|
||||
htmlparser2 "^6.1.0"
|
||||
parse5 "^6.0.1"
|
||||
parse5-htmlparser2-tree-adapter "^6.0.1"
|
||||
|
||||
"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.3.0, chokidar@^3.4.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
|
||||
@ -4787,6 +4810,17 @@ css-select@^2.0.0:
|
||||
domutils "^1.7.0"
|
||||
nth-check "^1.0.2"
|
||||
|
||||
css-select@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.1.2.tgz#8b52b6714ed3a80d8221ec971c543f3b12653286"
|
||||
integrity sha512-nu5ye2Hg/4ISq4XqdLY2bEatAcLIdt3OYGFc9Tm9n7VSlFBcfRv0gBNksHRgSdUDQGtN3XrZ94ztW+NfzkFSUw==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^5.0.0"
|
||||
domhandler "^4.2.0"
|
||||
domutils "^2.6.0"
|
||||
nth-check "^2.0.0"
|
||||
|
||||
css-selector-tokenizer@^0.7.1:
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1"
|
||||
@ -4816,6 +4850,11 @@ css-what@^3.2.1:
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
|
||||
integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ==
|
||||
|
||||
css-what@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.0.0.tgz#f0bf4f8bac07582722346ab243f6a35b512cfc47"
|
||||
integrity sha512-qxyKHQvgKwzwDWC/rGbT821eJalfupxYW2qbSJSAtdSTimsr/MlaGONoNLllaUPZWf8QnbcKM/kPVYUQuEKAFA==
|
||||
|
||||
css@^2.0.0:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929"
|
||||
@ -5298,6 +5337,15 @@ dom-serializer@0:
|
||||
domelementtype "^2.0.1"
|
||||
entities "^2.0.0"
|
||||
|
||||
dom-serializer@^1.0.1, dom-serializer@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.1.tgz#d845a1565d7c041a95e5dab62184ab41e3a519be"
|
||||
integrity sha512-Pv2ZluG5ife96udGgEDovOOOA5UELkltfJpnIExPrAk1LTvecolUGn6lIaoLh86d83GiB86CjzciMd9BuRB71Q==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
domain-browser@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
@ -5308,7 +5356,7 @@ domelementtype@1:
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f"
|
||||
integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==
|
||||
|
||||
domelementtype@^2.0.1:
|
||||
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57"
|
||||
integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A==
|
||||
@ -5320,6 +5368,13 @@ domexception@^2.0.1:
|
||||
dependencies:
|
||||
webidl-conversions "^5.0.0"
|
||||
|
||||
domhandler@^4.0.0, domhandler@^4.1.0, domhandler@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.0.tgz#f9768a5f034be60a89a27c2e4d0f74eba0d8b059"
|
||||
integrity sha512-zk7sgt970kzPks2Bf+dwT/PLzghLnsivb9CcxkvR8Mzr66Olr0Ofd8neSbglHJHaHa2MadfoSdNlKYAaafmWfA==
|
||||
dependencies:
|
||||
domelementtype "^2.2.0"
|
||||
|
||||
domutils@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||
@ -5328,6 +5383,15 @@ domutils@^1.7.0:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
domutils@^2.5.2, domutils@^2.6.0:
|
||||
version "2.6.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.6.0.tgz#2e15c04185d43fb16ae7057cb76433c6edb938b7"
|
||||
integrity sha512-y0BezHuy4MDYxh6OvolXYsH+1EMGmFbwv5FKW7ovwMG6zTPWqNPq3WF9ayZssFq+UlKdffGLbOEaghNdaOm1WA==
|
||||
dependencies:
|
||||
dom-serializer "^1.0.1"
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
dot-prop@^5.2.0:
|
||||
version "5.3.0"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88"
|
||||
@ -6847,6 +6911,16 @@ html-escaper@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
|
||||
|
||||
htmlparser2@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
|
||||
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.0.0"
|
||||
domutils "^2.5.2"
|
||||
entities "^2.0.0"
|
||||
|
||||
http-cache-semantics@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||
@ -9475,6 +9549,13 @@ nth-check@^1.0.2:
|
||||
dependencies:
|
||||
boolbase "~1.0.0"
|
||||
|
||||
nth-check@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.0.tgz#1bb4f6dac70072fc313e8c9cd1417b5074c0a125"
|
||||
integrity sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
|
||||
number-is-nan@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
|
Reference in New Issue
Block a user