Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
15a70abf67 | |||
5cb69291f5 | |||
cf582b2e98 | |||
82e159a083 | |||
2aff139982 | |||
6c7adb6193 | |||
bb2cd1c85a | |||
9d92c48ab7 | |||
dbed4ea527 | |||
8d149b5e2b | |||
27f1ec5d8a | |||
c361143ba2 | |||
069006145a |
29
CHANGELOG.md
29
CHANGELOG.md
@ -5,6 +5,35 @@ 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.90.0 - 22.04.2021
|
||||
|
||||
### Added
|
||||
|
||||
- Improved the user table of the admin control panel
|
||||
- Added the symbol logo to the position detail dialog
|
||||
- Introduced a third option for the market state: `delayed` (besides `open` and `closed`)
|
||||
|
||||
## 0.89.0 - 21.04.2021
|
||||
|
||||
### Added
|
||||
|
||||
- Added a prettifier (pipe) for generic scraper symbols
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the text truncation in buttons of the admin control panel
|
||||
|
||||
## 0.88.0 - 20.04.2021
|
||||
|
||||
### Changed
|
||||
|
||||
- Reverted the restoring of the scroll position when opening a new page
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the frozen screen if the token has expired
|
||||
- Fixed some issues in the generic scraper
|
||||
|
||||
## 0.87.0 - 19.04.2021
|
||||
|
||||
### Added
|
||||
|
13
README.md
13
README.md
@ -61,7 +61,7 @@ The frontend is built with [Angular](https://angular.io).
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
- [Node.js](https://nodejs.org/en/download) (version 14+)
|
||||
- [Yarn](https://yarnpkg.com/en/docs/install)
|
||||
- [Docker](https://www.docker.com/products/docker-desktop)
|
||||
|
||||
@ -71,12 +71,17 @@ The frontend is built with [Angular](https://angular.io).
|
||||
2. Run `cd docker`
|
||||
3. Run `docker compose build`
|
||||
4. Run `docker compose up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
|
||||
5. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
|
||||
6. Start server and client (see _Development_)
|
||||
7. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
|
||||
5. Run `cd -` to go back to the project root directory
|
||||
6. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
|
||||
7. Start server and client (see _Development_)
|
||||
8. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
|
||||
9. Go to the _Admin Control Panel_ and press _Gather All Data_ to fetch historical data
|
||||
10. Press _Sign out_ and check out the _Live Demo_
|
||||
|
||||
## Development
|
||||
|
||||
Please make sure you have completed the instructions from _Setup_
|
||||
|
||||
### Start server
|
||||
|
||||
- Debug: Run `yarn watch:server` and click "Launch Program" in _Visual Studio Code_
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
|
||||
import { AccessService } from './access.service';
|
||||
import { Access } from './interfaces/access.interface';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { AccessController } from './access.controller';
|
||||
import { AccessService } from './access.service';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { AccessWithGranteeUser } from './interfaces/access-with-grantee-user.type';
|
||||
|
||||
@Injectable()
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Controller,
|
||||
@ -9,10 +11,8 @@ import {
|
||||
} from '@nestjs/common';
|
||||
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 { DataGatheringService } from '../../services/data-gathering.service';
|
||||
import { AdminService } from './admin.service';
|
||||
import { AdminData } from './interfaces/admin-data.interface';
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
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';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { AdminController } from './admin.controller';
|
||||
import { AdminService } from './admin.service';
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Currency } from '@prisma/client';
|
||||
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { AdminData } from './interfaces/admin-data.interface';
|
||||
|
||||
@Injectable()
|
||||
@ -109,7 +109,12 @@ export class AdminService {
|
||||
createdAt: true,
|
||||
id: true
|
||||
},
|
||||
take: 20
|
||||
take: 20,
|
||||
where: {
|
||||
NOT: {
|
||||
Analytics: null
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
@ -10,7 +11,6 @@ import {
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Controller('auth')
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
import { ValidateOAuthLoginParams } from './interfaces/interfaces';
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Provider } from '@prisma/client';
|
||||
import { Strategy } from 'passport-google-oauth20';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Injectable()
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
|
||||
@Injectable()
|
||||
|
2
apps/api/src/app/cache/cache.controller.ts
vendored
2
apps/api/src/app/cache/cache.controller.ts
vendored
@ -1,7 +1,7 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import { Controller, Inject, Param, Post, UseGuards } from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
|
||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||
import { CacheService } from './cache.service';
|
||||
|
2
apps/api/src/app/cache/cache.module.ts
vendored
2
apps/api/src/app/cache/cache.module.ts
vendored
@ -1,6 +1,6 @@
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
||||
import { CacheController } from './cache.controller';
|
||||
import { CacheService } from './cache.service';
|
||||
|
4
apps/api/src/app/cache/cache.service.ts
vendored
4
apps/api/src/app/cache/cache.service.ts
vendored
@ -1,7 +1,5 @@
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, User } from '@prisma/client';
|
||||
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class CacheService {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import {
|
||||
baseCurrency,
|
||||
benchmarks,
|
||||
@ -14,7 +15,6 @@ import {
|
||||
Post
|
||||
} from '@nestjs/common';
|
||||
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';
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||
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';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { RulesService } from '../../services/rules.service';
|
||||
import { ExperimentalController } from './experimental.controller';
|
||||
import { ExperimentalService } from './experimental.service';
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Portfolio } from '@ghostfolio/api/models/portfolio';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Currency, Type } from '@prisma/client';
|
||||
import { parseISO } from 'date-fns';
|
||||
|
||||
import { Portfolio } from '../../models/portfolio';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { RulesService } from '../../services/rules.service';
|
||||
import { OrderWithPlatform } from '../order/interfaces/order-with-platform.type';
|
||||
import { CreateOrderDto } from './create-order.dto';
|
||||
import { Data } from './interfaces/data.interface';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { InfoController } from './info.controller';
|
||||
import { InfoService } from './info.service';
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { permissions } from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Currency } from '@prisma/client';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { InfoItem } from './interfaces/info-item.interface';
|
||||
|
||||
@Injectable()
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import { nullifyValuesInObjects } from '@ghostfolio/api/helper/object.helper';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Body,
|
||||
@ -15,12 +18,9 @@ import {
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
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 { nullifyValuesInObjects } from '../../helper/object.helper';
|
||||
import { ImpersonationService } from '../../services/impersonation.service';
|
||||
import { CreateOrderDto } from './create-order.dto';
|
||||
import { OrderService } from './order.service';
|
||||
import { UpdateOrderDto } from './update-order.dto';
|
||||
|
@ -1,14 +1,14 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
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';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { CacheService } from '../cache/cache.service';
|
||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
||||
import { OrderController } from './order.controller';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Order, Prisma } from '@prisma/client';
|
||||
|
||||
import { DataGatheringService } from '../../services/data-gathering.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { CacheService } from '../cache/cache.service';
|
||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||
import { OrderWithPlatform } from './interfaces/order-with-platform.type';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { Currency } from '@prisma/client';
|
||||
|
||||
export interface PortfolioPosition {
|
||||
@ -7,10 +8,10 @@ export interface PortfolioPosition {
|
||||
grossPerformancePercent: number;
|
||||
industry?: string;
|
||||
investment: number;
|
||||
isMarketOpen: boolean;
|
||||
marketChange?: number;
|
||||
marketChangePercent?: number;
|
||||
marketPrice: number;
|
||||
marketState: MarketState;
|
||||
name: string;
|
||||
platforms: {
|
||||
[name: string]: { current: number; original: number };
|
||||
|
@ -1,3 +1,9 @@
|
||||
import {
|
||||
hasNotDefinedValuesInObject,
|
||||
nullifyValuesInObject
|
||||
} from '@ghostfolio/api/helper/object.helper';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Controller,
|
||||
@ -15,12 +21,6 @@ import { AuthGuard } from '@nestjs/passport';
|
||||
import { Response } from 'express';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import {
|
||||
hasNotDefinedValuesInObject,
|
||||
nullifyValuesInObject
|
||||
} from '../../helper/object.helper';
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
import { ImpersonationService } from '../../services/impersonation.service';
|
||||
import { RequestWithUser } from '../interfaces/request-with-user.type';
|
||||
import { PortfolioItem } from './interfaces/portfolio-item.interface';
|
||||
import { PortfolioOverview } from './interfaces/portfolio-overview.interface';
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
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';
|
||||
import { ImpersonationService } from '../../services/impersonation.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { RulesService } from '../../services/rules.service';
|
||||
import { CacheService } from '../cache/cache.service';
|
||||
import { OrderService } from '../order/order.service';
|
||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import { Portfolio } from '@ghostfolio/api/models/portfolio';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
import { ImpersonationService } from '@ghostfolio/api/services/impersonation.service';
|
||||
import { IOrder } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { RulesService } from '@ghostfolio/api/services/rules.service';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
||||
import {
|
||||
add,
|
||||
format,
|
||||
@ -9,7 +15,6 @@ import {
|
||||
getYear,
|
||||
isAfter,
|
||||
isSameDay,
|
||||
parse,
|
||||
parseISO,
|
||||
setDate,
|
||||
setMonth,
|
||||
@ -18,12 +23,6 @@ import {
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as roundTo from 'round-to';
|
||||
|
||||
import { Portfolio } from '../../models/portfolio';
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { ExchangeRateDataService } from '../../services/exchange-rate-data.service';
|
||||
import { ImpersonationService } from '../../services/impersonation.service';
|
||||
import { IOrder } from '../../services/interfaces/interfaces';
|
||||
import { RulesService } from '../../services/rules.service';
|
||||
import { OrderService } from '../order/order.service';
|
||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||
import { UserService } from '../user/user.service';
|
||||
@ -48,53 +47,6 @@ export class PortfolioService {
|
||||
private readonly userService: UserService
|
||||
) {}
|
||||
|
||||
private convertDateRangeToDate(aDateRange: DateRange, aMinDate: Date) {
|
||||
let currentDate = new Date();
|
||||
|
||||
const normalizedMinDate =
|
||||
getDate(aMinDate) === 1
|
||||
? aMinDate
|
||||
: add(setDate(aMinDate, 1), { months: 1 });
|
||||
|
||||
const year = getYear(currentDate);
|
||||
const month = getMonth(currentDate);
|
||||
const day = getDate(currentDate);
|
||||
|
||||
currentDate = new Date(Date.UTC(year, month, day, 0));
|
||||
|
||||
switch (aDateRange) {
|
||||
case '1d':
|
||||
return sub(currentDate, {
|
||||
days: 1
|
||||
});
|
||||
case 'ytd':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = setMonth(currentDate, 0);
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '1y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 1
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '5y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 5
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
default:
|
||||
// Gets handled as all data
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public async createPortfolio(aUserId: string): Promise<Portfolio> {
|
||||
let portfolio: Portfolio;
|
||||
let stringifiedPortfolio = await this.redisCacheService.get(
|
||||
@ -382,4 +334,51 @@ export class PortfolioService {
|
||||
symbol: aSymbol
|
||||
};
|
||||
}
|
||||
|
||||
private convertDateRangeToDate(aDateRange: DateRange, aMinDate: Date) {
|
||||
let currentDate = new Date();
|
||||
|
||||
const normalizedMinDate =
|
||||
getDate(aMinDate) === 1
|
||||
? aMinDate
|
||||
: add(setDate(aMinDate, 1), { months: 1 });
|
||||
|
||||
const year = getYear(currentDate);
|
||||
const month = getMonth(currentDate);
|
||||
const day = getDate(currentDate);
|
||||
|
||||
currentDate = new Date(Date.UTC(year, month, day, 0));
|
||||
|
||||
switch (aDateRange) {
|
||||
case '1d':
|
||||
return sub(currentDate, {
|
||||
days: 1
|
||||
});
|
||||
case 'ytd':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = setMonth(currentDate, 0);
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '1y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 1
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '5y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 5
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
default:
|
||||
// Gets handled as all data
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { CacheModule, Module } from '@nestjs/common';
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||
import * as redisStore from 'cache-manager-redis-store';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { RedisCacheService } from './redis-cache.service';
|
||||
|
||||
@Module({
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
|
||||
import { Cache } from 'cache-manager';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
|
||||
@Injectable()
|
||||
export class RedisCacheService {
|
||||
public constructor(
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
} from '@nestjs/common';
|
||||
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 { LookupItem } from './interfaces/lookup-item.interface';
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { AlphaVantageService } from '@ghostfolio/api/services/data-provider/alpha-vantage/alpha-vantage.service';
|
||||
import { GhostfolioScraperApiService } from '@ghostfolio/api/services/data-provider/ghostfolio-scraper-api/ghostfolio-scraper-api.service';
|
||||
import { RakutenRapidApiService } from '@ghostfolio/api/services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
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';
|
||||
import { SymbolController } from './symbol.controller';
|
||||
import { SymbolService } from './symbol.service';
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider.service';
|
||||
import { convertFromYahooSymbol } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Currency } from '@prisma/client';
|
||||
import { convertFromYahooSymbol } from 'apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||
import * as bent from 'bent';
|
||||
|
||||
import { DataProviderService } from '../../services/data-provider.service';
|
||||
import { LookupItem } from './interfaces/lookup-item.interface';
|
||||
import { SymbolItem } from './interfaces/symbol-item.interface';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||
import {
|
||||
Body,
|
||||
@ -14,7 +15,6 @@ import { REQUEST } from '@nestjs/core';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
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 { UserItem } from './interfaces/user-item.interface';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { UserController } from './user.controller';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||
import {
|
||||
getPermissions,
|
||||
locale,
|
||||
@ -8,8 +10,6 @@ import { Injectable } from '@nestjs/common';
|
||||
import { Currency, Prisma, Provider, User } from '@prisma/client';
|
||||
import { add } from 'date-fns';
|
||||
|
||||
import { ConfigurationService } from '../../services/configuration.service';
|
||||
import { PrismaService } from '../../services/prisma.service';
|
||||
import { UserWithSettings } from '../interfaces/user-with-settings';
|
||||
import { User as IUser } from './interfaces/user.interface';
|
||||
|
||||
@ -44,10 +44,6 @@ export class UserService {
|
||||
currentPermissions.push(permissions.accessFearAndGreedIndex);
|
||||
}
|
||||
|
||||
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
|
||||
currentPermissions.push(permissions.useSocialLogin);
|
||||
}
|
||||
|
||||
return {
|
||||
alias,
|
||||
id,
|
||||
@ -162,18 +158,6 @@ export class UserService {
|
||||
});
|
||||
}
|
||||
|
||||
private getRandomString(length: number) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(
|
||||
characters.charAt(Math.floor(Math.random() * characters.length))
|
||||
);
|
||||
}
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
public async updateUserSettings({
|
||||
currency,
|
||||
userId
|
||||
@ -200,4 +184,16 @@ export class UserService {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private getRandomString(length: number) {
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
const result = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result.push(
|
||||
characters.charAt(Math.floor(Math.random() * characters.length))
|
||||
);
|
||||
}
|
||||
return result.join('');
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
PortfolioItem,
|
||||
Position
|
||||
} from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
||||
} from '@ghostfolio/api/app/portfolio/interfaces/portfolio-item.interface';
|
||||
|
||||
import { Order } from '../order';
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { PortfolioPosition } from '../../app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
|
||||
import { EvaluationResult } from './evaluation-result.interface';
|
||||
|
||||
export interface RuleInterface {
|
||||
|
@ -5,9 +5,11 @@ import { Currency, Role, Type } from '@prisma/client';
|
||||
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';
|
||||
import { MarketState } from '../services/interfaces/interfaces';
|
||||
import { PrismaService } from '../services/prisma.service';
|
||||
import { RulesService } from '../services/rules.service';
|
||||
import { Portfolio } from './portfolio';
|
||||
@ -17,6 +19,7 @@ describe('Portfolio', () => {
|
||||
let configurationService: ConfigurationService;
|
||||
let dataProviderService: DataProviderService;
|
||||
let exchangeRateDataService: ExchangeRateDataService;
|
||||
let ghostfolioScraperApiService: GhostfolioScraperApiService;
|
||||
let portfolio: Portfolio;
|
||||
let prismaService: PrismaService;
|
||||
let rakutenRapidApiService: RakutenRapidApiService;
|
||||
@ -31,6 +34,7 @@ describe('Portfolio', () => {
|
||||
ConfigurationService,
|
||||
DataProviderService,
|
||||
ExchangeRateDataService,
|
||||
GhostfolioScraperApiService,
|
||||
PrismaService,
|
||||
RakutenRapidApiService,
|
||||
RulesService,
|
||||
@ -44,6 +48,9 @@ describe('Portfolio', () => {
|
||||
exchangeRateDataService = app.get<ExchangeRateDataService>(
|
||||
ExchangeRateDataService
|
||||
);
|
||||
ghostfolioScraperApiService = app.get<GhostfolioScraperApiService>(
|
||||
GhostfolioScraperApiService
|
||||
);
|
||||
prismaService = app.get<PrismaService>(PrismaService);
|
||||
rakutenRapidApiService = app.get<RakutenRapidApiService>(
|
||||
RakutenRapidApiService
|
||||
@ -154,8 +161,8 @@ describe('Portfolio', () => {
|
||||
Currency.USD,
|
||||
baseCurrency
|
||||
),
|
||||
isMarketOpen: true,
|
||||
// marketPrice: 57973.008,
|
||||
marketState: MarketState.open,
|
||||
name: 'Bitcoin USD',
|
||||
platforms: {
|
||||
Other: {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { getToday, getYesterday, resetHours } from '@ghostfolio/helper';
|
||||
import {
|
||||
PortfolioItem,
|
||||
Position
|
||||
} from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
||||
} from '@ghostfolio/api/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import { getToday, getYesterday, resetHours } from '@ghostfolio/helper';
|
||||
import {
|
||||
add,
|
||||
format,
|
||||
@ -79,7 +79,9 @@ export class Portfolio implements PortfolioInterface {
|
||||
investmentInOriginalCurrency:
|
||||
portfolioItemsYesterday?.positions[symbol]
|
||||
?.investmentInOriginalCurrency,
|
||||
marketPrice: currentData[symbol]?.marketPrice,
|
||||
marketPrice:
|
||||
currentData[symbol]?.marketPrice ??
|
||||
portfolioItemsYesterday.positions[symbol]?.marketPrice,
|
||||
quantity: portfolioItemsYesterday?.positions[symbol]?.quantity
|
||||
};
|
||||
});
|
||||
@ -158,53 +160,6 @@ export class Portfolio implements PortfolioInterface {
|
||||
return this;
|
||||
}
|
||||
|
||||
private convertDateRangeToDate(aDateRange: DateRange, aMinDate: Date) {
|
||||
let currentDate = new Date();
|
||||
|
||||
const normalizedMinDate =
|
||||
getDate(aMinDate) === 1
|
||||
? aMinDate
|
||||
: add(setDate(aMinDate, 1), { months: 1 });
|
||||
|
||||
const year = getYear(currentDate);
|
||||
const month = getMonth(currentDate);
|
||||
const day = getDate(currentDate);
|
||||
|
||||
currentDate = new Date(Date.UTC(year, month, day, 0));
|
||||
|
||||
switch (aDateRange) {
|
||||
case '1d':
|
||||
return sub(currentDate, {
|
||||
days: 1
|
||||
});
|
||||
case 'ytd':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = setMonth(currentDate, 0);
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '1y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 1
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '5y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 5
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
default:
|
||||
// Gets handled as all data
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public get(aDate?: Date): PortfolioItem[] {
|
||||
if (aDate) {
|
||||
const filteredPortfolio = this.portfolioItems.find((item) => {
|
||||
@ -528,12 +483,6 @@ export class Portfolio implements PortfolioInterface {
|
||||
return this.orders;
|
||||
}
|
||||
|
||||
private getOrdersByType(aFilter: string[]) {
|
||||
return this.orders.filter((order) => {
|
||||
return aFilter.includes(order.getType());
|
||||
});
|
||||
}
|
||||
|
||||
public getValue(aDate = getToday()) {
|
||||
const positions = this.getPositions(aDate);
|
||||
let value = 0;
|
||||
@ -692,6 +641,53 @@ export class Portfolio implements PortfolioInterface {
|
||||
this.updatePortfolioItems();
|
||||
}
|
||||
|
||||
private convertDateRangeToDate(aDateRange: DateRange, aMinDate: Date) {
|
||||
let currentDate = new Date();
|
||||
|
||||
const normalizedMinDate =
|
||||
getDate(aMinDate) === 1
|
||||
? aMinDate
|
||||
: add(setDate(aMinDate, 1), { months: 1 });
|
||||
|
||||
const year = getYear(currentDate);
|
||||
const month = getMonth(currentDate);
|
||||
const day = getDate(currentDate);
|
||||
|
||||
currentDate = new Date(Date.UTC(year, month, day, 0));
|
||||
|
||||
switch (aDateRange) {
|
||||
case '1d':
|
||||
return sub(currentDate, {
|
||||
days: 1
|
||||
});
|
||||
case 'ytd':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = setMonth(currentDate, 0);
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '1y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 1
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
case '5y':
|
||||
currentDate = setDate(currentDate, 1);
|
||||
currentDate = sub(currentDate, {
|
||||
years: 5
|
||||
});
|
||||
return isAfter(currentDate, normalizedMinDate)
|
||||
? currentDate
|
||||
: undefined;
|
||||
default:
|
||||
// Gets handled as all data
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private updatePortfolioItems() {
|
||||
// console.time('update-portfolio-items');
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||
|
||||
import { Rule } from '../../rule';
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
benchmarks,
|
||||
currencyPairs,
|
||||
getUtc,
|
||||
isGhostfolioScraperApiSymbol,
|
||||
resetHours
|
||||
} from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
@ -235,12 +236,16 @@ export class DataGatheringService {
|
||||
select: { symbol: true }
|
||||
});
|
||||
|
||||
const distinctOrdersWithDate = distinctOrders.map((distinctOrder) => {
|
||||
return {
|
||||
...distinctOrder,
|
||||
date: startDate
|
||||
};
|
||||
});
|
||||
const distinctOrdersWithDate = distinctOrders
|
||||
.filter((distinctOrder) => {
|
||||
return !isGhostfolioScraperApiSymbol(distinctOrder.symbol);
|
||||
})
|
||||
.map((distinctOrder) => {
|
||||
return {
|
||||
...distinctOrder,
|
||||
date: startDate
|
||||
};
|
||||
});
|
||||
|
||||
const currencyPairsToGather = currencyPairs.map((symbol) => {
|
||||
return {
|
||||
@ -262,7 +267,7 @@ export class DataGatheringService {
|
||||
}
|
||||
|
||||
private async getSymbolsMax() {
|
||||
const startDate = new Date(getUtc('2000-01-01'));
|
||||
const startDate = new Date(getUtc('2015-01-01'));
|
||||
|
||||
const customSymbolsToGather = await this.getCustomSymbolsToGather(
|
||||
startDate
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {
|
||||
isCrypto,
|
||||
isGhostfolioScraperApi,
|
||||
isRakutenRapidApi
|
||||
isGhostfolioScraperApiSymbol,
|
||||
isRakutenRapidApiSymbol
|
||||
} from '@ghostfolio/helper';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { MarketData } from '@prisma/client';
|
||||
@ -39,14 +39,33 @@ export class DataProviderService implements DataProviderInterface {
|
||||
if (aSymbols.length === 1) {
|
||||
const symbol = aSymbols[0];
|
||||
|
||||
if (isGhostfolioScraperApi(symbol)) {
|
||||
if (isGhostfolioScraperApiSymbol(symbol)) {
|
||||
return this.ghostfolioScraperApiService.get(aSymbols);
|
||||
} else if (isRakutenRapidApi(symbol)) {
|
||||
} else if (isRakutenRapidApiSymbol(symbol)) {
|
||||
return this.rakutenRapidApiService.get(aSymbols);
|
||||
}
|
||||
}
|
||||
|
||||
return this.yahooFinanceService.get(aSymbols);
|
||||
const yahooFinanceSymbols = aSymbols.filter((symbol) => {
|
||||
return !isGhostfolioScraperApiSymbol(symbol);
|
||||
});
|
||||
|
||||
const response = await this.yahooFinanceService.get(yahooFinanceSymbols);
|
||||
|
||||
const ghostfolioScraperApiSymbols = aSymbols.filter((symbol) => {
|
||||
return isGhostfolioScraperApiSymbol(symbol);
|
||||
});
|
||||
|
||||
for (const symbol of ghostfolioScraperApiSymbols) {
|
||||
if (symbol) {
|
||||
const ghostfolioScraperApiResult = await this.ghostfolioScraperApiService.get(
|
||||
[symbol]
|
||||
);
|
||||
response[symbol] = ghostfolioScraperApiResult[symbol];
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async getHistorical(
|
||||
@ -107,8 +126,12 @@ export class DataProviderService implements DataProviderInterface {
|
||||
): Promise<{
|
||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||
}> {
|
||||
const filteredSymbols = aSymbols.filter((symbol) => {
|
||||
return !isGhostfolioScraperApiSymbol(symbol);
|
||||
});
|
||||
|
||||
const dataOfYahoo = await this.yahooFinanceService.getHistorical(
|
||||
aSymbols,
|
||||
filteredSymbols,
|
||||
undefined,
|
||||
from,
|
||||
to
|
||||
@ -135,7 +158,7 @@ export class DataProviderService implements DataProviderInterface {
|
||||
...dataOfAlphaVantage[symbol]
|
||||
}
|
||||
};
|
||||
} else if (isGhostfolioScraperApi(symbol)) {
|
||||
} else if (isGhostfolioScraperApiSymbol(symbol)) {
|
||||
const dataOfGhostfolioScraperApi = await this.ghostfolioScraperApiService.getHistorical(
|
||||
[symbol],
|
||||
undefined,
|
||||
@ -145,7 +168,7 @@ export class DataProviderService implements DataProviderInterface {
|
||||
|
||||
return dataOfGhostfolioScraperApi;
|
||||
} else if (
|
||||
isRakutenRapidApi(symbol) &&
|
||||
isRakutenRapidApiSymbol(symbol) &&
|
||||
this.configurationService.get('RAKUTEN_RAPID_API_KEY')
|
||||
) {
|
||||
const dataOfRakutenRapidApi = await this.rakutenRapidApiService.getHistorical(
|
||||
|
@ -8,10 +8,10 @@ import { DataProviderInterface } from '../../interfaces/data-provider.interface'
|
||||
import { Granularity } from '../../interfaces/granularity.type';
|
||||
import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse
|
||||
IDataProviderResponse,
|
||||
MarketState
|
||||
} from '../../interfaces/interfaces';
|
||||
import { PrismaService } from '../../prisma.service';
|
||||
import { Currency } from '.prisma/client';
|
||||
|
||||
@Injectable()
|
||||
export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
@ -26,6 +26,9 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
|
||||
try {
|
||||
const symbol = aSymbols[0];
|
||||
|
||||
const scraperConfig = await this.getScraperConfig(symbol);
|
||||
|
||||
const { marketPrice } = await this.prisma.marketData.findFirst({
|
||||
orderBy: {
|
||||
date: 'desc'
|
||||
@ -38,9 +41,9 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
return {
|
||||
[symbol]: {
|
||||
marketPrice,
|
||||
currency: Currency.CHF,
|
||||
isMarketOpen: true,
|
||||
name: symbol
|
||||
currency: scraperConfig?.currency,
|
||||
marketState: MarketState.delayed,
|
||||
name: scraperConfig?.name
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
@ -65,25 +68,17 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
try {
|
||||
const symbol = aSymbols[0];
|
||||
|
||||
const {
|
||||
value: scraperConfigString
|
||||
} = await this.prisma.property.findFirst({
|
||||
select: {
|
||||
value: true
|
||||
},
|
||||
where: { key: 'SCRAPER_CONFIG' }
|
||||
});
|
||||
const scraperConfig = await this.getScraperConfig(symbol);
|
||||
|
||||
const scraperConfig = JSON.parse(scraperConfigString).find((item) => {
|
||||
return item.symbol === symbol;
|
||||
});
|
||||
|
||||
const get = bent(scraperConfig.url, 'GET', 'string', 200, {});
|
||||
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 string = $(scraperConfig?.selector)
|
||||
.text()
|
||||
.replace('CHF', '')
|
||||
.trim();
|
||||
|
||||
const value = parseFloat(string);
|
||||
|
||||
@ -100,4 +95,23 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
private async getScraperConfig(aSymbol: string) {
|
||||
try {
|
||||
const {
|
||||
value: scraperConfigString
|
||||
} = await this.prisma.property.findFirst({
|
||||
select: {
|
||||
value: true
|
||||
},
|
||||
where: { key: 'SCRAPER_CONFIG' }
|
||||
});
|
||||
|
||||
return JSON.parse(scraperConfigString).find((item) => {
|
||||
return item.symbol === aSymbol;
|
||||
});
|
||||
} catch {}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,8 @@ import { DataProviderInterface } from '../../interfaces/data-provider.interface'
|
||||
import { Granularity } from '../../interfaces/granularity.type';
|
||||
import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse
|
||||
IDataProviderResponse,
|
||||
MarketState
|
||||
} from '../../interfaces/interfaces';
|
||||
import { PrismaService } from '../../prisma.service';
|
||||
|
||||
@ -38,8 +39,8 @@ export class RakutenRapidApiService implements DataProviderInterface {
|
||||
return {
|
||||
'GF.FEAR_AND_GREED_INDEX': {
|
||||
currency: undefined,
|
||||
isMarketOpen: true,
|
||||
marketPrice: fgi.now.value,
|
||||
marketState: MarketState.open,
|
||||
name: RakutenRapidApiService.FEAR_AND_GREED_INDEX_NAME
|
||||
}
|
||||
};
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse,
|
||||
Industry,
|
||||
MarketState,
|
||||
Sector,
|
||||
Type
|
||||
} from '../../interfaces/interfaces';
|
||||
@ -49,8 +50,10 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
response[symbol] = {
|
||||
currency: parseCurrency(value.price?.currency),
|
||||
exchange: this.parseExchange(value.price?.exchangeName),
|
||||
isMarketOpen:
|
||||
value.price?.marketState === 'REGULAR' || isCrypto(symbol),
|
||||
marketState:
|
||||
value.price?.marketState === 'REGULAR' || isCrypto(symbol)
|
||||
? MarketState.open
|
||||
: MarketState.closed,
|
||||
marketPrice: value.price?.regularMarketPrice || 0,
|
||||
name: value.price?.longName || value.price?.shortName || symbol,
|
||||
type: this.parseType(this.getType(symbol, value))
|
||||
|
@ -12,6 +12,12 @@ export const Industry = {
|
||||
Software: 'Software'
|
||||
};
|
||||
|
||||
export const MarketState = {
|
||||
closed: 'closed',
|
||||
delayed: 'delayed',
|
||||
open: 'open'
|
||||
};
|
||||
|
||||
export const Sector = {
|
||||
Consumer: 'Consumer',
|
||||
Healthcare: 'Healthcare',
|
||||
@ -47,10 +53,10 @@ export interface IDataProviderResponse {
|
||||
currency: Currency;
|
||||
exchange?: string;
|
||||
industry?: Industry;
|
||||
isMarketOpen: boolean;
|
||||
marketChange?: number;
|
||||
marketChangePercent?: number;
|
||||
marketPrice: number;
|
||||
marketState: MarketState;
|
||||
name: string;
|
||||
sector?: Sector;
|
||||
type?: Type;
|
||||
@ -59,6 +65,8 @@ export interface IDataProviderResponse {
|
||||
|
||||
export type Industry = typeof Industry[keyof typeof Industry];
|
||||
|
||||
export type MarketState = typeof MarketState[keyof typeof MarketState];
|
||||
|
||||
export type Sector = typeof Sector[keyof typeof Sector];
|
||||
|
||||
export type Type = typeof Type[keyof typeof Type];
|
||||
|
@ -81,8 +81,7 @@ const routes: Routes = [
|
||||
{
|
||||
preloadingStrategy: ModulePreloadService,
|
||||
// enableTracing: true // <-- debugging purposes only
|
||||
relativeLinkResolution: 'legacy',
|
||||
scrollPositionRestoration: 'enabled'
|
||||
relativeLinkResolution: 'legacy'
|
||||
}
|
||||
)
|
||||
],
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { NavigationEnd, Router } from '@angular/router';
|
||||
import { InfoItem } from '@ghostfolio/api/app/info/interfaces/info-item.interface';
|
||||
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||
import {
|
||||
hasPermission,
|
||||
permissions,
|
||||
@ -13,8 +15,6 @@ import {
|
||||
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 { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Access } from 'apps/api/src/app/access/interfaces/access.interface';
|
||||
import { Access } from '@ghostfolio/api/app/access/interfaces/access.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-access-table',
|
||||
|
@ -1,3 +1,9 @@
|
||||
<gf-symbol-icon
|
||||
*ngIf="symbolUrl"
|
||||
class="mr-1"
|
||||
size="large"
|
||||
[url]="symbolUrl"
|
||||
></gf-symbol-icon>
|
||||
<span class="flex-grow-1 text-truncate">{{ title }}</span>
|
||||
<button
|
||||
*ngIf="deviceType !== 'mobile'"
|
||||
|
@ -1,3 +1,4 @@
|
||||
:host {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
})
|
||||
export class DialogHeaderComponent implements OnInit {
|
||||
@Input() deviceType: string;
|
||||
@Input() symbolUrl: string;
|
||||
@Input() title: string;
|
||||
|
||||
@Output() closeButtonClicked = new EventEmitter<void>();
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { GfSymbolIconModule } from '@ghostfolio/client/components/symbol-icon/symbol-icon.module';
|
||||
|
||||
import { DialogHeaderComponent } from './dialog-header.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [DialogHeaderComponent],
|
||||
exports: [DialogHeaderComponent],
|
||||
imports: [CommonModule, MatButtonModule],
|
||||
imports: [CommonModule, GfSymbolIconModule, MatButtonModule],
|
||||
providers: [],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
|
@ -6,17 +6,16 @@ import {
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { InfoItem } from '@ghostfolio/api/app/info/interfaces/info-item.interface';
|
||||
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||
import { LoginWithAccessTokenDialog } from '@ghostfolio/client/pages/login/login-with-access-token-dialog/login-with-access-token-dialog.component';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
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 { EMPTY, Subject } from 'rxjs';
|
||||
import { catchError, takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { LoginWithAccessTokenDialog } from '../../pages/login/login-with-access-token-dialog/login-with-access-token-dialog.component';
|
||||
import { DataService } from '../../services/data.service';
|
||||
import { ImpersonationStorageService } from '../../services/impersonation-storage.service';
|
||||
import { TokenStorageService } from '../../services/token-storage.service';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-header',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
|
@ -4,8 +4,8 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { LoginWithAccessTokenDialogModule } from '@ghostfolio/client/pages/login/login-with-access-token-dialog/login-with-access-token-dialog.module';
|
||||
|
||||
import { LoginWithAccessTokenDialogModule } from '../../pages/login/login-with-access-token-dialog/login-with-access-token-dialog.module';
|
||||
import { GfLogoModule } from '../logo/logo.module';
|
||||
import { HeaderComponent } from './header.component';
|
||||
|
||||
|
@ -9,8 +9,8 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { PortfolioItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import { primaryColorRgb } from '@ghostfolio/helper';
|
||||
import { PortfolioItem } from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import {
|
||||
LineController,
|
||||
LineElement,
|
||||
|
@ -5,9 +5,9 @@ import {
|
||||
Inject
|
||||
} from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { isToday, parse } from 'date-fns';
|
||||
|
||||
import { DataService } from '../../services/data.service';
|
||||
import { LineChartItem } from '../line-chart/interfaces/line-chart.interface';
|
||||
import { PositionDetailDialogParams } from './interfaces/interfaces';
|
||||
|
||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { GfLineChartModule } from '../../components/line-chart/line-chart.module';
|
||||
import { GfDialogFooterModule } from '../dialog-footer/dialog-footer.module';
|
||||
import { GfDialogHeaderModule } from '../dialog-header/dialog-header.module';
|
||||
import { GfFearAndGreedIndexModule } from '../fear-and-greed-index/fear-and-greed-index.module';
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
OnChanges,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { PortfolioOverview } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-overview.interface';
|
||||
import { Currency } from '@prisma/client';
|
||||
import { PortfolioOverview } from 'apps/api/src/app/portfolio/interfaces/portfolio-overview.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-portfolio-overview',
|
||||
|
@ -7,8 +7,8 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { PortfolioPerformance } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-performance.interface';
|
||||
import { Currency } from '@prisma/client';
|
||||
import { PortfolioPerformance } from 'apps/api/src/app/portfolio/interfaces/portfolio-performance.interface';
|
||||
import { CountUp } from 'countup.js';
|
||||
import { isNumber } from 'lodash';
|
||||
|
||||
|
@ -4,8 +4,8 @@ import {
|
||||
Input,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { PortfolioPerformance } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-performance.interface';
|
||||
import { Currency } from '@prisma/client';
|
||||
import { PortfolioPerformance } from 'apps/api/src/app/portfolio/interfaces/portfolio-performance.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-portfolio-performance',
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
OnChanges,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { PortfolioItem } from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import { PortfolioItem } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-item.interface';
|
||||
import { endOfDay, parseISO, startOfDay } from 'date-fns';
|
||||
|
||||
@Component({
|
||||
|
@ -7,8 +7,8 @@ import {
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { Currency } from '@prisma/client';
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { Tooltip } from 'chart.js';
|
||||
import { LinearScale } from 'chart.js';
|
||||
import { ArcElement } from 'chart.js';
|
||||
|
@ -3,5 +3,6 @@ export interface PositionDetailDialogParams {
|
||||
deviceType: string;
|
||||
locale: string;
|
||||
symbol: string;
|
||||
symbolUrl: string;
|
||||
title: string;
|
||||
}
|
||||
|
@ -5,9 +5,9 @@ import {
|
||||
Inject
|
||||
} from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { format, isSameMonth, isToday, parseISO } from 'date-fns';
|
||||
|
||||
import { DataService } from '../../../services/data.service';
|
||||
import { LineChartItem } from '../../line-chart/interfaces/line-chart.interface';
|
||||
import { PositionDetailDialogParams } from './interfaces/interfaces';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<gf-dialog-header
|
||||
mat-dialog-title
|
||||
[deviceType]="data.deviceType"
|
||||
[symbolUrl]="data.symbolUrl"
|
||||
[title]="data.title"
|
||||
(closeButtonClicked)="onClose()"
|
||||
></gf-dialog-header>
|
||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { GfLineChartModule } from '../../../components/line-chart/line-chart.module';
|
||||
import { GfDialogFooterModule } from '../../dialog-footer/dialog-footer.module';
|
||||
import { GfDialogHeaderModule } from '../../dialog-header/dialog-header.module';
|
||||
import { GfValueModule } from '../../value/value.module';
|
||||
|
@ -9,7 +9,7 @@
|
||||
<gf-trend-indicator
|
||||
class="d-flex"
|
||||
[isLoading]="isLoading"
|
||||
[isPaused]="!position?.isMarketOpen"
|
||||
[marketState]="position?.marketState"
|
||||
[range]="range"
|
||||
[value]="position?.grossPerformancePercent"
|
||||
></gf-trend-indicator>
|
||||
@ -34,13 +34,10 @@
|
||||
<div *ngIf="!isLoading" class="flex-grow-1 text-truncate">
|
||||
<div class="h6 m-0 text-truncate">{{ position?.name }}</div>
|
||||
<div class="d-flex">
|
||||
<span>{{ position?.symbol }}</span>
|
||||
<gf-symbol-icon
|
||||
*ngIf="position?.url"
|
||||
class="ml-1"
|
||||
[url]="position?.url"
|
||||
></gf-symbol-icon>
|
||||
<span class="ml-2 text-muted">({{ position?.exchange }})</span>
|
||||
<span>{{ position?.symbol | gfSymbol }}</span>
|
||||
<span *ngIf="position?.exchange" class="ml-2 text-muted"
|
||||
>({{ position.exchange }})</span
|
||||
>
|
||||
</div>
|
||||
<div class="d-flex mt-1">
|
||||
<gf-value
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -51,6 +51,11 @@ export class PositionComponent implements OnDestroy, OnInit {
|
||||
|
||||
public ngOnInit() {}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
|
||||
private openDialog(): void {
|
||||
const dialogRef = this.dialog.open(PositionDetailDialog, {
|
||||
autoFocus: false,
|
||||
@ -59,6 +64,7 @@ export class PositionComponent implements OnDestroy, OnInit {
|
||||
deviceType: this.deviceType,
|
||||
locale: this.locale,
|
||||
symbol: this.position?.symbol,
|
||||
symbolUrl: this.position?.url,
|
||||
title: this.position?.name
|
||||
},
|
||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
||||
@ -69,9 +75,4 @@ export class PositionComponent implements OnDestroy, OnInit {
|
||||
this.router.navigate(['.'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
this.unsubscribeSubject.complete();
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||
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 { GfPositionDetailDialogModule } from './position-detail-dialog/position-detail-dialog.module';
|
||||
@ -16,7 +16,7 @@ import { PositionComponent } from './position.component';
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfPositionDetailDialogModule,
|
||||
GfSymbolIconModule,
|
||||
GfSymbolModule,
|
||||
GfTrendIndicatorModule,
|
||||
GfValueModule,
|
||||
MatDialogModule,
|
||||
|
@ -13,7 +13,7 @@
|
||||
>
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header i18n>Symbol</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol | gfSymbol }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="performance">
|
||||
|
@ -13,8 +13,8 @@ import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { Order as OrderModel } from '@prisma/client';
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { Subject, Subscription } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
|
@ -7,6 +7,7 @@ import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { GfNoTransactionsInfoModule } from '../no-transactions-info/no-transactions-info.module';
|
||||
@ -23,6 +24,7 @@ import { PositionsTableComponent } from './positions-table.component';
|
||||
GfNoTransactionsInfoModule,
|
||||
GfPositionDetailDialogModule,
|
||||
GfSymbolIconModule,
|
||||
GfSymbolModule,
|
||||
GfValueModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
|
@ -5,7 +5,8 @@ import {
|
||||
OnChanges,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-positions',
|
||||
@ -40,7 +41,10 @@ export class PositionsComponent implements OnChanges, OnInit {
|
||||
this.positionsWithPriority = [];
|
||||
|
||||
for (const [, portfolioPosition] of Object.entries(this.positions)) {
|
||||
if (portfolioPosition.isMarketOpen || this.range !== '1d') {
|
||||
if (
|
||||
portfolioPosition.marketState === MarketState.open ||
|
||||
this.range !== '1d'
|
||||
) {
|
||||
// Only show positions where the market is open in today's view
|
||||
this.positionsWithPriority.push(portfolioPosition);
|
||||
} else {
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
Input,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { PortfolioReportRule } from 'apps/api/src/app/portfolio/interfaces/portfolio-report.interface';
|
||||
import { PortfolioReportRule } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-report.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-rule',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
||||
import { PortfolioReportRule } from 'apps/api/src/app/portfolio/interfaces/portfolio-report.interface';
|
||||
import { PortfolioReportRule } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-report.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-rules',
|
||||
|
@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { GfRuleModule } from 'apps/client/src/app/components/rule/rule.module';
|
||||
import { GfRuleModule } from '@ghostfolio/client/components/rule/rule.module';
|
||||
|
||||
import { GfNoTransactionsInfoModule } from '../no-transactions-info/no-transactions-info.module';
|
||||
import { GfPositionModule } from '../position/position.module';
|
||||
|
@ -1,5 +1,6 @@
|
||||
<img
|
||||
*ngIf="url"
|
||||
src="https://www.google.com/s2/favicons?domain={{ url }}&sz=64"
|
||||
[ngClass]="{ large: size === 'large' }"
|
||||
[title]="tooltip ? tooltip : ''"
|
||||
/>
|
||||
|
@ -5,5 +5,10 @@
|
||||
border-radius: 0.2rem;
|
||||
height: 0.8rem;
|
||||
width: 0.8rem;
|
||||
|
||||
&.large {
|
||||
height: 1.4rem;
|
||||
width: 1.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
styleUrls: ['./symbol-icon.component.scss']
|
||||
})
|
||||
export class SymbolIconComponent implements OnInit {
|
||||
@Input() size: 'large';
|
||||
@Input() tooltip: string;
|
||||
@Input() url: string;
|
||||
|
||||
|
@ -83,7 +83,7 @@
|
||||
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th *matHeaderCellDef i18n mat-header-cell mat-sort-header>Symbol</th>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
|
||||
<td mat-cell *matCellDef="let element">{{ element.symbol | gfSymbol }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="currency">
|
||||
@ -192,7 +192,7 @@
|
||||
<tr
|
||||
*matRowDef="let row; columns: displayedColumns"
|
||||
mat-row
|
||||
(click)="onOpenPositionDialog({ symbol: row.symbol, title: row.symbol })"
|
||||
(click)="onOpenPositionDialog({ symbol: row.symbol, title: '' })"
|
||||
></tr>
|
||||
</table>
|
||||
|
||||
|
@ -55,11 +55,7 @@ export class TransactionsTableComponent
|
||||
this.routeQueryParams = route.queryParams
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((params) => {
|
||||
if (
|
||||
params['positionDetailDialog'] &&
|
||||
params['symbol'] &&
|
||||
params['title']
|
||||
) {
|
||||
if (params['positionDetailDialog'] && params['symbol']) {
|
||||
this.openPositionDialog({
|
||||
symbol: params['symbol'],
|
||||
title: params['title']
|
||||
|
@ -6,6 +6,7 @@ import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||
|
||||
import { GfPositionDetailDialogModule } from '../position/position-detail-dialog/position-detail-dialog.module';
|
||||
@ -20,6 +21,7 @@ import { TransactionsTableComponent } from './transactions-table.component';
|
||||
CommonModule,
|
||||
GfPositionDetailDialogModule,
|
||||
GfSymbolIconModule,
|
||||
GfSymbolModule,
|
||||
GfValueModule,
|
||||
MatButtonModule,
|
||||
MatInputModule,
|
||||
|
@ -10,12 +10,21 @@
|
||||
|
||||
<ng-template #other>
|
||||
<ion-icon
|
||||
*ngIf="isPaused && range === '1d'; else trend"
|
||||
*ngIf="marketState === 'closed' && range === '1d'; else delayed"
|
||||
class="text-muted"
|
||||
name="pause-circle-outline"
|
||||
size="large"
|
||||
>
|
||||
</ion-icon>
|
||||
<ng-template #delayed>
|
||||
<ion-icon
|
||||
*ngIf="marketState === 'delayed' && range === '1d'; else trend"
|
||||
class="text-muted"
|
||||
name="time-outline"
|
||||
size="large"
|
||||
>
|
||||
</ion-icon>
|
||||
</ng-template>
|
||||
<ng-template #trend>
|
||||
<ng-container>
|
||||
<ion-icon
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
Input,
|
||||
OnInit
|
||||
} from '@angular/core';
|
||||
import { MarketState } from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-trend-indicator',
|
||||
@ -13,7 +14,7 @@ import {
|
||||
})
|
||||
export class TrendIndicatorComponent implements OnInit {
|
||||
@Input() isLoading: boolean;
|
||||
@Input() isPaused: boolean;
|
||||
@Input() marketState: MarketState;
|
||||
@Input() range: string;
|
||||
@Input() value: number;
|
||||
|
||||
|
@ -45,21 +45,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
||||
authReq = req.clone({ headers });
|
||||
}
|
||||
|
||||
return next.handle(authReq).pipe(
|
||||
tap(
|
||||
() => {},
|
||||
(err: any) => {
|
||||
if (err instanceof HttpErrorResponse) {
|
||||
if (err.status !== 401) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.tokenStorageService.signOut();
|
||||
this.router.navigate(['start']);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
return next.handle(authReq);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,15 +15,22 @@ import {
|
||||
MatSnackBarRef,
|
||||
TextOnlySnackBar
|
||||
} from '@angular/material/snack-bar';
|
||||
import { Router } from '@angular/router';
|
||||
import { StatusCodes } from 'http-status-codes';
|
||||
import { Observable, throwError } from 'rxjs';
|
||||
import { catchError, tap } from 'rxjs/operators';
|
||||
|
||||
import { TokenStorageService } from '../services/token-storage.service';
|
||||
|
||||
@Injectable()
|
||||
export class HttpResponseInterceptor implements HttpInterceptor {
|
||||
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;
|
||||
|
||||
public constructor(private snackBar: MatSnackBar) {}
|
||||
public constructor(
|
||||
private router: Router,
|
||||
private tokenStorageService: TokenStorageService,
|
||||
private snackBar: MatSnackBar
|
||||
) {}
|
||||
|
||||
public intercept(
|
||||
request: HttpRequest<any>,
|
||||
@ -70,6 +77,9 @@ export class HttpResponseInterceptor implements HttpInterceptor {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
} else if (error.status === StatusCodes.UNAUTHORIZED) {
|
||||
this.tokenStorageService.signOut();
|
||||
this.router.navigate(['start']);
|
||||
}
|
||||
|
||||
return throwError('');
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
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 { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { DataService } from '../../services/data.service';
|
||||
import { TokenStorageService } from '../../services/token-storage.service';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'gf-about-page',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { AuthGuard } from '../../core/auth.guard';
|
||||
import { AccountPageComponent } from './account-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Access } from '@ghostfolio/api/app/access/interfaces/access.interface';
|
||||
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||
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 { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
import { DataService } from '../../services/data.service';
|
||||
import { TokenStorageService } from '../../services/token-storage.service';
|
||||
import { Currency } from '.prisma/client';
|
||||
|
||||
@Component({
|
||||
|
@ -4,8 +4,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { GfPortfolioAccessTableModule } from '@ghostfolio/client/components/access-table/access-table.module';
|
||||
|
||||
import { GfPortfolioAccessTableModule } from '../../components/access-table/access-table.module';
|
||||
import { AccountPageRoutingModule } from './account-page-routing.module';
|
||||
import { AccountPageComponent } from './account-page.component';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { AuthGuard } from '../../core/auth.guard';
|
||||
import { AdminPageComponent } from './admin-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user