Compare commits
16 Commits
Author | SHA1 | Date | |
---|---|---|---|
2aff139982 | |||
6c7adb6193 | |||
bb2cd1c85a | |||
9d92c48ab7 | |||
dbed4ea527 | |||
8d149b5e2b | |||
27f1ec5d8a | |||
c361143ba2 | |||
069006145a | |||
3e3395aff9 | |||
a2687eacbc | |||
0f2c8c856c | |||
ec4dbf2a51 | |||
3d34aa5e80 | |||
c45bd70711 | |||
9f876e6020 |
33
CHANGELOG.md
33
CHANGELOG.md
@ -5,6 +5,37 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- 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
|
## 0.86.1 - 18.04.2021
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -18,7 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Changed the about page for the new license
|
- Changed the about page for the new license
|
||||||
- Optimized the data management for historical data
|
- Optimized the data management for historical data
|
||||||
- Optimized the exchange rate service
|
- Optimized the exchange rate service
|
||||||
- Improved the user table in the admin control panel
|
- Improved the user table of the admin control panel
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
25
README.md
25
README.md
@ -45,23 +45,23 @@ Ghostfolio is for you if you are...
|
|||||||
- ✅ Static analysis to identify potential risks in your portfolio
|
- ✅ Static analysis to identify potential risks in your portfolio
|
||||||
- ✅ Dark Mode
|
- ✅ 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.
|
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
|
### 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.
|
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
|
## Getting Started
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/en/download)
|
- [Node.js](https://nodejs.org/en/download) (version 14+)
|
||||||
- [Yarn](https://yarnpkg.com/en/docs/install)
|
- [Yarn](https://yarnpkg.com/en/docs/install)
|
||||||
- [Docker](https://www.docker.com/products/docker-desktop)
|
- [Docker](https://www.docker.com/products/docker-desktop)
|
||||||
|
|
||||||
@ -71,12 +71,17 @@ The backend is based on [NestJS](https://nestjs.com) using [PostgreSQL](https://
|
|||||||
2. Run `cd docker`
|
2. Run `cd docker`
|
||||||
3. Run `docker compose build`
|
3. Run `docker compose build`
|
||||||
4. Run `docker compose up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
|
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
|
5. Run `cd -` to go back to the project root directory
|
||||||
6. Start server and client (see _Development_)
|
6. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
|
||||||
7. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
|
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
|
## Development
|
||||||
|
|
||||||
|
Please make sure you have completed the instructions from _Setup_
|
||||||
|
|
||||||
### Start server
|
### Start server
|
||||||
|
|
||||||
- Debug: Run `yarn watch:server` and click "Launch Program" in _Visual Studio Code_
|
- Debug: Run `yarn watch:server` and click "Launch Program" in _Visual Studio Code_
|
||||||
@ -92,4 +97,6 @@ Run `yarn test`
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
© 2021 [Ghostfolio](https://ghostfol.io)
|
||||||
|
|
||||||
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).
|
Licensed under the [AGPLv3 License](https://www.gnu.org/licenses/agpl-3.0.html).
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||||
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
|
import { Controller, Get, Inject, UseGuards } from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
|
||||||
|
|
||||||
import { AccessService } from './access.service';
|
import { AccessService } from './access.service';
|
||||||
import { Access } from './interfaces/access.interface';
|
import { Access } from './interfaces/access.interface';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { AccessController } from './access.controller';
|
import { AccessController } from './access.controller';
|
||||||
import { AccessService } from './access.service';
|
import { AccessService } from './access.service';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { AccessWithGranteeUser } from './interfaces/access-with-grantee-user.type';
|
import { AccessWithGranteeUser } from './interfaces/access-with-grantee-user.type';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
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 {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
@ -8,11 +11,8 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
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 { 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';
|
import { AdminService } from './admin.service';
|
||||||
import { AdminData } from './interfaces/admin-data.interface';
|
import { AdminData } from './interfaces/admin-data.interface';
|
||||||
|
|
||||||
|
@ -1,13 +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 { 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 { 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 { AdminController } from './admin.controller';
|
||||||
import { AdminService } from './admin.service';
|
import { AdminService } from './admin.service';
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ import { AdminService } from './admin.service';
|
|||||||
DataGatheringService,
|
DataGatheringService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
ExchangeRateDataService,
|
ExchangeRateDataService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
RakutenRapidApiService,
|
RakutenRapidApiService,
|
||||||
YahooFinanceService
|
YahooFinanceService
|
||||||
|
@ -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 { Injectable } from '@nestjs/common';
|
||||||
import { Currency } from '@prisma/client';
|
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';
|
import { AdminData } from './interfaces/admin-data.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -10,6 +10,7 @@ import { CronService } from '../services/cron.service';
|
|||||||
import { DataGatheringService } from '../services/data-gathering.service';
|
import { DataGatheringService } from '../services/data-gathering.service';
|
||||||
import { DataProviderService } from '../services/data-provider.service';
|
import { DataProviderService } from '../services/data-provider.service';
|
||||||
import { AlphaVantageService } from '../services/data-provider/alpha-vantage/alpha-vantage.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 { RakutenRapidApiService } from '../services/data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||||
import { YahooFinanceService } from '../services/data-provider/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceService } from '../services/data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
||||||
@ -65,6 +66,7 @@ import { UserModule } from './user/user.module';
|
|||||||
DataGatheringService,
|
DataGatheringService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
ExchangeRateDataService,
|
ExchangeRateDataService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
RakutenRapidApiService,
|
RakutenRapidApiService,
|
||||||
YahooFinanceService
|
YahooFinanceService
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
@ -10,7 +11,6 @@ import {
|
|||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
@Controller('auth')
|
@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 { Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { UserService } from '../user/user.service';
|
import { UserService } from '../user/user.service';
|
||||||
import { AuthController } from './auth.controller';
|
import { AuthController } from './auth.controller';
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { UserService } from '../user/user.service';
|
import { UserService } from '../user/user.service';
|
||||||
import { ValidateOAuthLoginParams } from './interfaces/interfaces';
|
import { ValidateOAuthLoginParams } from './interfaces/interfaces';
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Provider } from '@prisma/client';
|
import { Provider } from '@prisma/client';
|
||||||
import { Strategy } from 'passport-google-oauth20';
|
import { Strategy } from 'passport-google-oauth20';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { AuthService } from './auth.service';
|
import { AuthService } from './auth.service';
|
||||||
|
|
||||||
@Injectable()
|
@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 { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { UserService } from '../user/user.service';
|
import { UserService } from '../user/user.service';
|
||||||
|
|
||||||
@Injectable()
|
@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 { Controller, Inject, Param, Post, UseGuards } from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
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 { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||||
import { CacheService } from './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 { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
||||||
import { CacheController } from './cache.controller';
|
import { CacheController } from './cache.controller';
|
||||||
import { CacheService } from './cache.service';
|
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 { Injectable } from '@nestjs/common';
|
||||||
import { Prisma, User } from '@prisma/client';
|
|
||||||
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CacheService {
|
export class CacheService {
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||||
|
import {
|
||||||
|
baseCurrency,
|
||||||
|
benchmarks,
|
||||||
|
isApiTokenAuthorized
|
||||||
|
} from '@ghostfolio/helper';
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
@ -9,11 +15,8 @@ import {
|
|||||||
Post
|
Post
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
|
||||||
import { parse } from 'date-fns';
|
import { parse } from 'date-fns';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
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 { CreateOrderDto } from './create-order.dto';
|
||||||
import { ExperimentalService } from './experimental.service';
|
import { ExperimentalService } from './experimental.service';
|
||||||
|
@ -1,13 +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 { 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 { 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 { ExperimentalController } from './experimental.controller';
|
||||||
import { ExperimentalService } from './experimental.service';
|
import { ExperimentalService } from './experimental.service';
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ import { ExperimentalService } from './experimental.service';
|
|||||||
DataProviderService,
|
DataProviderService,
|
||||||
ExchangeRateDataService,
|
ExchangeRateDataService,
|
||||||
ExperimentalService,
|
ExperimentalService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
RakutenRapidApiService,
|
RakutenRapidApiService,
|
||||||
RulesService,
|
RulesService,
|
||||||
|
@ -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 { Injectable } from '@nestjs/common';
|
||||||
import { Currency, Type } from '@prisma/client';
|
import { Currency, Type } from '@prisma/client';
|
||||||
import { parseISO } from 'date-fns';
|
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 { OrderWithPlatform } from '../order/interfaces/order-with-platform.type';
|
||||||
import { CreateOrderDto } from './create-order.dto';
|
import { CreateOrderDto } from './create-order.dto';
|
||||||
import { Data } from './interfaces/data.interface';
|
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 { Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { InfoController } from './info.controller';
|
import { InfoController } from './info.controller';
|
||||||
import { InfoService } from './info.service';
|
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 { permissions } from '@ghostfolio/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { InfoItem } from './interfaces/info-item.interface';
|
import { InfoItem } from './interfaces/info-item.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
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 {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
@ -14,13 +18,9 @@ import {
|
|||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { Order as OrderModel } from '@prisma/client';
|
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 { parseISO } from 'date-fns';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
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';
|
|
||||||
import { CreateOrderDto } from './create-order.dto';
|
import { CreateOrderDto } from './create-order.dto';
|
||||||
import { OrderService } from './order.service';
|
import { OrderService } from './order.service';
|
||||||
import { UpdateOrderDto } from './update-order.dto';
|
import { UpdateOrderDto } from './update-order.dto';
|
||||||
|
@ -1,13 +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 { 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 { 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 { CacheService } from '../cache/cache.service';
|
||||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
||||||
import { OrderController } from './order.controller';
|
import { OrderController } from './order.controller';
|
||||||
@ -22,6 +23,7 @@ import { OrderService } from './order.service';
|
|||||||
ConfigurationService,
|
ConfigurationService,
|
||||||
DataGatheringService,
|
DataGatheringService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
ImpersonationService,
|
ImpersonationService,
|
||||||
OrderService,
|
OrderService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
|
@ -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 { Injectable } from '@nestjs/common';
|
||||||
import { Order, Prisma } from '@prisma/client';
|
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 { CacheService } from '../cache/cache.service';
|
||||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||||
import { OrderWithPlatform } from './interfaces/order-with-platform.type';
|
import { OrderWithPlatform } from './interfaces/order-with-platform.type';
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
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 {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
@ -13,14 +20,7 @@ import { REQUEST } from '@nestjs/core';
|
|||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
import { getPermissions, hasPermission, permissions } from 'libs/helper/src';
|
|
||||||
|
|
||||||
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 { RequestWithUser } from '../interfaces/request-with-user.type';
|
||||||
import { PortfolioItem } from './interfaces/portfolio-item.interface';
|
import { PortfolioItem } from './interfaces/portfolio-item.interface';
|
||||||
import { PortfolioOverview } from './interfaces/portfolio-overview.interface';
|
import { PortfolioOverview } from './interfaces/portfolio-overview.interface';
|
||||||
|
@ -1,15 +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 { 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 { 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 { CacheService } from '../cache/cache.service';
|
||||||
import { OrderService } from '../order/order.service';
|
import { OrderService } from '../order/order.service';
|
||||||
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
|
||||||
@ -27,6 +28,7 @@ import { PortfolioService } from './portfolio.service';
|
|||||||
DataGatheringService,
|
DataGatheringService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
ExchangeRateDataService,
|
ExchangeRateDataService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
ImpersonationService,
|
ImpersonationService,
|
||||||
OrderService,
|
OrderService,
|
||||||
PortfolioService,
|
PortfolioService,
|
||||||
|
@ -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 { Inject, Injectable } from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { RequestWithUser } from 'apps/api/src/app/interfaces/request-with-user.type';
|
|
||||||
import {
|
import {
|
||||||
add,
|
add,
|
||||||
format,
|
format,
|
||||||
@ -9,7 +15,6 @@ import {
|
|||||||
getYear,
|
getYear,
|
||||||
isAfter,
|
isAfter,
|
||||||
isSameDay,
|
isSameDay,
|
||||||
parse,
|
|
||||||
parseISO,
|
parseISO,
|
||||||
setDate,
|
setDate,
|
||||||
setMonth,
|
setMonth,
|
||||||
@ -18,12 +23,6 @@ import {
|
|||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import * as roundTo from 'round-to';
|
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 { OrderService } from '../order/order.service';
|
||||||
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
import { RedisCacheService } from '../redis-cache/redis-cache.service';
|
||||||
import { UserService } from '../user/user.service';
|
import { UserService } from '../user/user.service';
|
||||||
@ -48,53 +47,6 @@ export class PortfolioService {
|
|||||||
private readonly userService: UserService
|
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> {
|
public async createPortfolio(aUserId: string): Promise<Portfolio> {
|
||||||
let portfolio: Portfolio;
|
let portfolio: Portfolio;
|
||||||
let stringifiedPortfolio = await this.redisCacheService.get(
|
let stringifiedPortfolio = await this.redisCacheService.get(
|
||||||
@ -382,4 +334,51 @@ export class PortfolioService {
|
|||||||
symbol: aSymbol
|
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 { CacheModule, Module } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import * as redisStore from 'cache-manager-redis-store';
|
import * as redisStore from 'cache-manager-redis-store';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { RedisCacheService } from './redis-cache.service';
|
import { RedisCacheService } from './redis-cache.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
|
import { CACHE_MANAGER, Inject, Injectable } from '@nestjs/common';
|
||||||
import { Cache } from 'cache-manager';
|
import { Cache } from 'cache-manager';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RedisCacheService {
|
export class RedisCacheService {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
Get,
|
Get,
|
||||||
@ -9,7 +10,6 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
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 { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { LookupItem } from './interfaces/lookup-item.interface';
|
import { LookupItem } from './interfaces/lookup-item.interface';
|
||||||
|
@ -1,11 +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 { 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 { 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 { SymbolController } from './symbol.controller';
|
||||||
import { SymbolService } from './symbol.service';
|
import { SymbolService } from './symbol.service';
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ import { SymbolService } from './symbol.service';
|
|||||||
AlphaVantageService,
|
AlphaVantageService,
|
||||||
ConfigurationService,
|
ConfigurationService,
|
||||||
DataProviderService,
|
DataProviderService,
|
||||||
|
GhostfolioScraperApiService,
|
||||||
PrismaService,
|
PrismaService,
|
||||||
RakutenRapidApiService,
|
RakutenRapidApiService,
|
||||||
SymbolService,
|
SymbolService,
|
||||||
|
@ -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 { Injectable } from '@nestjs/common';
|
||||||
import { Currency } from '@prisma/client';
|
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 * as bent from 'bent';
|
||||||
|
|
||||||
import { DataProviderService } from '../../services/data-provider.service';
|
|
||||||
import { LookupItem } from './interfaces/lookup-item.interface';
|
import { LookupItem } from './interfaces/lookup-item.interface';
|
||||||
import { SymbolItem } from './interfaces/symbol-item.interface';
|
import { SymbolItem } from './interfaces/symbol-item.interface';
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { RequestWithUser } from '@ghostfolio/api/app/interfaces/request-with-user.type';
|
||||||
|
import { getPermissions, hasPermission, permissions } from '@ghostfolio/helper';
|
||||||
import {
|
import {
|
||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
@ -13,9 +15,7 @@ import { REQUEST } from '@nestjs/core';
|
|||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { Provider } from '@prisma/client';
|
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 { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
import { getPermissions, hasPermission, permissions } from 'libs/helper/src';
|
|
||||||
|
|
||||||
import { UserItem } from './interfaces/user-item.interface';
|
import { UserItem } from './interfaces/user-item.interface';
|
||||||
import { User } from './interfaces/user.interface';
|
import { User } from './interfaces/user.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 { Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
|
||||||
import { ConfigurationService } from '../../services/configuration.service';
|
|
||||||
import { PrismaService } from '../../services/prisma.service';
|
|
||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
|
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration.service';
|
||||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
|
||||||
|
import {
|
||||||
|
getPermissions,
|
||||||
|
locale,
|
||||||
|
permissions,
|
||||||
|
resetHours
|
||||||
|
} from '@ghostfolio/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Currency, Prisma, Provider, User } from '@prisma/client';
|
import { Currency, Prisma, Provider, User } from '@prisma/client';
|
||||||
import { add } from 'date-fns';
|
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';
|
|
||||||
import { UserWithSettings } from '../interfaces/user-with-settings';
|
import { UserWithSettings } from '../interfaces/user-with-settings';
|
||||||
import { User as IUser } from './interfaces/user.interface';
|
import { User as IUser } from './interfaces/user.interface';
|
||||||
|
|
||||||
@ -40,10 +44,6 @@ export class UserService {
|
|||||||
currentPermissions.push(permissions.accessFearAndGreedIndex);
|
currentPermissions.push(permissions.accessFearAndGreedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.configurationService.get('ENABLE_FEATURE_SOCIAL_LOGIN')) {
|
|
||||||
currentPermissions.push(permissions.useSocialLogin);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
alias,
|
alias,
|
||||||
id,
|
id,
|
||||||
@ -158,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({
|
public async updateUserSettings({
|
||||||
currency,
|
currency,
|
||||||
userId
|
userId
|
||||||
@ -196,4 +184,16 @@ export class UserService {
|
|||||||
|
|
||||||
return;
|
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 {
|
import {
|
||||||
PortfolioItem,
|
PortfolioItem,
|
||||||
Position
|
Position
|
||||||
} from 'apps/api/src/app/portfolio/interfaces/portfolio-item.interface';
|
} from '@ghostfolio/api/app/portfolio/interfaces/portfolio-item.interface';
|
||||||
|
|
||||||
import { Order } from '../order';
|
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';
|
import { EvaluationResult } from './evaluation-result.interface';
|
||||||
|
|
||||||
export interface RuleInterface {
|
export interface RuleInterface {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
|
import { baseCurrency, getUtc, getYesterday } from '@ghostfolio/helper';
|
||||||
import { Test } from '@nestjs/testing';
|
import { Test } from '@nestjs/testing';
|
||||||
import { Currency, Role, Type } from '@prisma/client';
|
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 { ConfigurationService } from '../services/configuration.service';
|
||||||
import { DataProviderService } from '../services/data-provider.service';
|
import { DataProviderService } from '../services/data-provider.service';
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
PortfolioItem,
|
PortfolioItem,
|
||||||
Position
|
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 {
|
import {
|
||||||
add,
|
add,
|
||||||
format,
|
format,
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
setMonth,
|
setMonth,
|
||||||
sub
|
sub
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { getToday, getYesterday, resetHours } from 'libs/helper/src';
|
|
||||||
import { cloneDeep, isEmpty } from 'lodash';
|
import { cloneDeep, isEmpty } from 'lodash';
|
||||||
import * as roundTo from 'round-to';
|
import * as roundTo from 'round-to';
|
||||||
|
|
||||||
@ -79,7 +79,9 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
investmentInOriginalCurrency:
|
investmentInOriginalCurrency:
|
||||||
portfolioItemsYesterday?.positions[symbol]
|
portfolioItemsYesterday?.positions[symbol]
|
||||||
?.investmentInOriginalCurrency,
|
?.investmentInOriginalCurrency,
|
||||||
marketPrice: currentData[symbol]?.marketPrice,
|
marketPrice:
|
||||||
|
currentData[symbol]?.marketPrice ??
|
||||||
|
portfolioItemsYesterday.positions[symbol]?.marketPrice,
|
||||||
quantity: portfolioItemsYesterday?.positions[symbol]?.quantity
|
quantity: portfolioItemsYesterday?.positions[symbol]?.quantity
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -158,53 +160,6 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
return this;
|
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[] {
|
public get(aDate?: Date): PortfolioItem[] {
|
||||||
if (aDate) {
|
if (aDate) {
|
||||||
const filteredPortfolio = this.portfolioItems.find((item) => {
|
const filteredPortfolio = this.portfolioItems.find((item) => {
|
||||||
@ -528,12 +483,6 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
return this.orders;
|
return this.orders;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getOrdersByType(aFilter: string[]) {
|
|
||||||
return this.orders.filter((order) => {
|
|
||||||
return aFilter.includes(order.getType());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public getValue(aDate = getToday()) {
|
public getValue(aDate = getToday()) {
|
||||||
const positions = this.getPositions(aDate);
|
const positions = this.getPositions(aDate);
|
||||||
let value = 0;
|
let value = 0;
|
||||||
@ -692,6 +641,53 @@ export class Portfolio implements PortfolioInterface {
|
|||||||
this.updatePortfolioItems();
|
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() {
|
private updatePortfolioItems() {
|
||||||
// console.time('update-portfolio-items');
|
// console.time('update-portfolio-items');
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
import { groupBy } from '@ghostfolio/helper';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { groupBy } from 'libs/helper/src';
|
|
||||||
|
|
||||||
import { PortfolioPosition } from '../app/portfolio/interfaces/portfolio-position.interface';
|
import { PortfolioPosition } from '../app/portfolio/interfaces/portfolio-position.interface';
|
||||||
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '../services/exchange-rate-data.service';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
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 { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
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 { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
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 { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
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 { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
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 { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
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 { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
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 { ExchangeRateDataService } from 'apps/api/src/services/exchange-rate-data.service';
|
||||||
|
|
||||||
import { Rule } from '../../rule';
|
import { Rule } from '../../rule';
|
||||||
|
@ -12,6 +12,7 @@ export class ConfigurationService {
|
|||||||
ACCESS_TOKEN_SALT: str(),
|
ACCESS_TOKEN_SALT: str(),
|
||||||
ALPHA_VANTAGE_API_KEY: str({ default: '' }),
|
ALPHA_VANTAGE_API_KEY: str({ default: '' }),
|
||||||
CACHE_TTL: num({ default: 1 }),
|
CACHE_TTL: num({ default: 1 }),
|
||||||
|
ENABLE_FEATURE_CUSTOM_SYMBOLS: bool({ default: false }),
|
||||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: bool({ default: false }),
|
||||||
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
ENABLE_FEATURE_SOCIAL_LOGIN: bool({ default: false }),
|
||||||
GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }),
|
GOOGLE_CLIENT_ID: str({ default: 'dummyClientId' }),
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
import {
|
||||||
|
benchmarks,
|
||||||
|
currencyPairs,
|
||||||
|
getUtc,
|
||||||
|
isGhostfolioScraperApiSymbol,
|
||||||
|
resetHours
|
||||||
|
} from '@ghostfolio/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
differenceInHours,
|
differenceInHours,
|
||||||
@ -8,8 +15,6 @@ import {
|
|||||||
isBefore,
|
isBefore,
|
||||||
subDays
|
subDays
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { benchmarks, currencyPairs } from 'libs/helper/src';
|
|
||||||
import { getUtc, resetHours } from 'libs/helper/src';
|
|
||||||
|
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { DataProviderService } from './data-provider.service';
|
import { DataProviderService } from './data-provider.service';
|
||||||
@ -196,16 +201,46 @@ export class DataGatheringService {
|
|||||||
return benchmarksToGather;
|
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 }[]> {
|
private async getSymbols7D(): Promise<{ date: Date; symbol: string }[]> {
|
||||||
const startDate = subDays(resetHours(new Date()), 7);
|
const startDate = subDays(resetHours(new Date()), 7);
|
||||||
|
|
||||||
let distinctOrders = await this.prisma.order.findMany({
|
const distinctOrders = await this.prisma.order.findMany({
|
||||||
distinct: ['symbol'],
|
distinct: ['symbol'],
|
||||||
orderBy: [{ symbol: 'asc' }],
|
orderBy: [{ symbol: 'asc' }],
|
||||||
select: { symbol: true }
|
select: { symbol: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
const distinctOrdersWithDate = distinctOrders.map((distinctOrder) => {
|
const distinctOrdersWithDate = distinctOrders
|
||||||
|
.filter((distinctOrder) => {
|
||||||
|
return !isGhostfolioScraperApiSymbol(distinctOrder.symbol);
|
||||||
|
})
|
||||||
|
.map((distinctOrder) => {
|
||||||
return {
|
return {
|
||||||
...distinctOrder,
|
...distinctOrder,
|
||||||
date: startDate
|
date: startDate
|
||||||
@ -219,21 +254,24 @@ export class DataGatheringService {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const customSymbolsToGather = await this.getCustomSymbolsToGather(
|
||||||
|
startDate
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...this.getBenchmarksToGather(startDate),
|
...this.getBenchmarksToGather(startDate),
|
||||||
|
...customSymbolsToGather,
|
||||||
...currencyPairsToGather,
|
...currencyPairsToGather,
|
||||||
...distinctOrdersWithDate
|
...distinctOrdersWithDate
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSymbolsMax() {
|
private async getSymbolsMax() {
|
||||||
const startDate = new Date(getUtc('2000-01-01'));
|
const startDate = new Date(getUtc('2015-01-01'));
|
||||||
|
|
||||||
let distinctOrders = await this.prisma.order.findMany({
|
const customSymbolsToGather = await this.getCustomSymbolsToGather(
|
||||||
distinct: ['symbol'],
|
startDate
|
||||||
orderBy: [{ date: 'asc' }],
|
);
|
||||||
select: { date: true, symbol: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
const currencyPairsToGather = currencyPairs.map((symbol) => {
|
const currencyPairsToGather = currencyPairs.map((symbol) => {
|
||||||
return {
|
return {
|
||||||
@ -242,8 +280,15 @@ export class DataGatheringService {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const distinctOrders = await this.prisma.order.findMany({
|
||||||
|
distinct: ['symbol'],
|
||||||
|
orderBy: [{ date: 'asc' }],
|
||||||
|
select: { date: true, symbol: true }
|
||||||
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...this.getBenchmarksToGather(startDate),
|
...this.getBenchmarksToGather(startDate),
|
||||||
|
...customSymbolsToGather,
|
||||||
...currencyPairsToGather,
|
...currencyPairsToGather,
|
||||||
...distinctOrders
|
...distinctOrders
|
||||||
];
|
];
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import { isCrypto, isRakutenRapidApi } from '@ghostfolio/helper';
|
import {
|
||||||
|
isCrypto,
|
||||||
|
isGhostfolioScraperApiSymbol,
|
||||||
|
isRakutenRapidApiSymbol
|
||||||
|
} from '@ghostfolio/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { MarketData } from '@prisma/client';
|
import { MarketData } from '@prisma/client';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
import { ConfigurationService } from './configuration.service';
|
import { ConfigurationService } from './configuration.service';
|
||||||
import { AlphaVantageService } from './data-provider/alpha-vantage/alpha-vantage.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 { RakutenRapidApiService } from './data-provider/rakuten-rapid-api/rakuten-rapid-api.service';
|
||||||
import { YahooFinanceService } from './data-provider/yahoo-finance/yahoo-finance.service';
|
import { YahooFinanceService } from './data-provider/yahoo-finance/yahoo-finance.service';
|
||||||
import { DataProviderInterface } from './interfaces/data-provider.interface';
|
import { DataProviderInterface } from './interfaces/data-provider.interface';
|
||||||
@ -20,6 +25,7 @@ export class DataProviderService implements DataProviderInterface {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly alphaVantageService: AlphaVantageService,
|
private readonly alphaVantageService: AlphaVantageService,
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
|
private readonly ghostfolioScraperApiService: GhostfolioScraperApiService,
|
||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private readonly rakutenRapidApiService: RakutenRapidApiService,
|
private readonly rakutenRapidApiService: RakutenRapidApiService,
|
||||||
private readonly yahooFinanceService: YahooFinanceService
|
private readonly yahooFinanceService: YahooFinanceService
|
||||||
@ -33,12 +39,33 @@ export class DataProviderService implements DataProviderInterface {
|
|||||||
if (aSymbols.length === 1) {
|
if (aSymbols.length === 1) {
|
||||||
const symbol = aSymbols[0];
|
const symbol = aSymbols[0];
|
||||||
|
|
||||||
if (isRakutenRapidApi(symbol)) {
|
if (isGhostfolioScraperApiSymbol(symbol)) {
|
||||||
|
return this.ghostfolioScraperApiService.get(aSymbols);
|
||||||
|
} else if (isRakutenRapidApiSymbol(symbol)) {
|
||||||
return this.rakutenRapidApiService.get(aSymbols);
|
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(
|
public async getHistorical(
|
||||||
@ -53,12 +80,12 @@ export class DataProviderService implements DataProviderInterface {
|
|||||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
let granularityQuery =
|
const granularityQuery =
|
||||||
aGranularity === 'month'
|
aGranularity === 'month'
|
||||||
? `AND (date_part('day', date) = 1 OR date >= TIMESTAMP 'yesterday')`
|
? `AND (date_part('day', date) = 1 OR date >= TIMESTAMP 'yesterday')`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
let rangeQuery =
|
const rangeQuery =
|
||||||
from && to
|
from && to
|
||||||
? `AND date >= '${format(from, 'yyyy-MM-dd')}' AND date <= '${format(
|
? `AND date >= '${format(from, 'yyyy-MM-dd')}' AND date <= '${format(
|
||||||
to,
|
to,
|
||||||
@ -99,8 +126,12 @@ export class DataProviderService implements DataProviderInterface {
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||||
}> {
|
}> {
|
||||||
|
const filteredSymbols = aSymbols.filter((symbol) => {
|
||||||
|
return !isGhostfolioScraperApiSymbol(symbol);
|
||||||
|
});
|
||||||
|
|
||||||
const dataOfYahoo = await this.yahooFinanceService.getHistorical(
|
const dataOfYahoo = await this.yahooFinanceService.getHistorical(
|
||||||
aSymbols,
|
filteredSymbols,
|
||||||
undefined,
|
undefined,
|
||||||
from,
|
from,
|
||||||
to
|
to
|
||||||
@ -127,8 +158,17 @@ export class DataProviderService implements DataProviderInterface {
|
|||||||
...dataOfAlphaVantage[symbol]
|
...dataOfAlphaVantage[symbol]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
} else if (isGhostfolioScraperApiSymbol(symbol)) {
|
||||||
|
const dataOfGhostfolioScraperApi = await this.ghostfolioScraperApiService.getHistorical(
|
||||||
|
[symbol],
|
||||||
|
undefined,
|
||||||
|
from,
|
||||||
|
to
|
||||||
|
);
|
||||||
|
|
||||||
|
return dataOfGhostfolioScraperApi;
|
||||||
} else if (
|
} else if (
|
||||||
isRakutenRapidApi(symbol) &&
|
isRakutenRapidApiSymbol(symbol) &&
|
||||||
this.configurationService.get('RAKUTEN_RAPID_API_KEY')
|
this.configurationService.get('RAKUTEN_RAPID_API_KEY')
|
||||||
) {
|
) {
|
||||||
const dataOfRakutenRapidApi = await this.rakutenRapidApiService.getHistorical(
|
const dataOfRakutenRapidApi = await this.rakutenRapidApiService.getHistorical(
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
@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 scraperConfig = await this.getScraperConfig(symbol);
|
||||||
|
|
||||||
|
const { marketPrice } = await this.prisma.marketData.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
date: 'desc'
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
symbol
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
[symbol]: {
|
||||||
|
marketPrice,
|
||||||
|
currency: scraperConfig?.currency,
|
||||||
|
isMarketOpen: false,
|
||||||
|
name: scraperConfig?.name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} 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 scraperConfig = await this.getScraperConfig(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 {};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {};
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
|
import { getToday, getYesterday } from '@ghostfolio/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import * as bent from 'bent';
|
import * as bent from 'bent';
|
||||||
import { format, subMonths, subWeeks, subYears } from 'date-fns';
|
import { format, subMonths, subWeeks, subYears } from 'date-fns';
|
||||||
import { getToday, getYesterday } from 'libs/helper/src';
|
|
||||||
|
|
||||||
import { ConfigurationService } from '../../configuration.service';
|
import { ConfigurationService } from '../../configuration.service';
|
||||||
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import { isCrypto, isCurrency, parseCurrency } from '@ghostfolio/helper';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { isCrypto, isCurrency, parseCurrency } from 'libs/helper/src';
|
|
||||||
import * as yahooFinance from 'yahoo-finance';
|
import * as yahooFinance from 'yahoo-finance';
|
||||||
|
|
||||||
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
import { DataProviderInterface } from '../../interfaces/data-provider.interface';
|
||||||
|
@ -58,8 +58,8 @@ export class ExchangeRateDataService {
|
|||||||
if (!this.currencies[pair]) {
|
if (!this.currencies[pair]) {
|
||||||
// Not found, calculate indirectly via USD
|
// Not found, calculate indirectly via USD
|
||||||
this.currencies[pair] =
|
this.currencies[pair] =
|
||||||
resultExtended[`${currency1}${Currency.USD}`][date].marketPrice *
|
resultExtended[`${currency1}${Currency.USD}`]?.[date]?.marketPrice *
|
||||||
resultExtended[`${Currency.USD}${currency2}`][date].marketPrice;
|
resultExtended[`${Currency.USD}${currency2}`]?.[date]?.marketPrice;
|
||||||
|
|
||||||
// Calculate the opposite direction
|
// Calculate the opposite direction
|
||||||
this.currencies[`${currency2}${currency1}`] = 1 / this.currencies[pair];
|
this.currencies[`${currency2}${currency1}`] = 1 / this.currencies[pair];
|
||||||
|
@ -4,6 +4,7 @@ export interface Environment extends CleanedEnvAccessors {
|
|||||||
ACCESS_TOKEN_SALT: string;
|
ACCESS_TOKEN_SALT: string;
|
||||||
ALPHA_VANTAGE_API_KEY: string;
|
ALPHA_VANTAGE_API_KEY: string;
|
||||||
CACHE_TTL: number;
|
CACHE_TTL: number;
|
||||||
|
ENABLE_FEATURE_CUSTOM_SYMBOLS: boolean;
|
||||||
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
ENABLE_FEATURE_FEAR_AND_GREED_INDEX: boolean;
|
||||||
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
ENABLE_FEATURE_SOCIAL_LOGIN: boolean;
|
||||||
GOOGLE_CLIENT_ID: string;
|
GOOGLE_CLIENT_ID: string;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
DEFAULT_DATE_FORMAT,
|
DEFAULT_DATE_FORMAT,
|
||||||
DEFAULT_DATE_FORMAT_MONTH_YEAR
|
DEFAULT_DATE_FORMAT_MONTH_YEAR
|
||||||
} from 'libs/helper/src';
|
} from '@ghostfolio/helper';
|
||||||
|
|
||||||
export const DateFormats = {
|
export const DateFormats = {
|
||||||
display: {
|
display: {
|
||||||
|
@ -81,8 +81,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
preloadingStrategy: ModulePreloadService,
|
preloadingStrategy: ModulePreloadService,
|
||||||
// enableTracing: true // <-- debugging purposes only
|
// enableTracing: true // <-- debugging purposes only
|
||||||
relativeLinkResolution: 'legacy',
|
relativeLinkResolution: 'legacy'
|
||||||
scrollPositionRestoration: 'enabled'
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -6,11 +6,15 @@ import {
|
|||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NavigationEnd, Router } from '@angular/router';
|
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,
|
||||||
|
primaryColorHex,
|
||||||
|
secondaryColorHex
|
||||||
|
} from '@ghostfolio/helper';
|
||||||
import { MaterialCssVarsService } from 'angular-material-css-vars';
|
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 { Subject } from 'rxjs';
|
||||||
import { filter, takeUntil } from 'rxjs/operators';
|
import { filter, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
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({
|
@Component({
|
||||||
selector: 'gf-access-table',
|
selector: 'gf-access-table',
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { resolveFearAndGreedIndex } from 'libs/helper/src';
|
import { resolveFearAndGreedIndex } from '@ghostfolio/helper';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-fear-and-greed-index',
|
selector: 'gf-fear-and-greed-index',
|
||||||
|
@ -6,17 +6,16 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { InfoItem } from 'apps/api/src/app/info/interfaces/info-item.interface';
|
import { InfoItem } from '@ghostfolio/api/app/info/interfaces/info-item.interface';
|
||||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||||
import { hasPermission, permissions } from 'libs/helper/src';
|
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 { EMPTY, Subject } from 'rxjs';
|
import { EMPTY, Subject } from 'rxjs';
|
||||||
import { catchError, takeUntil } from 'rxjs/operators';
|
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({
|
@Component({
|
||||||
selector: 'gf-header',
|
selector: 'gf-header',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
@ -4,8 +4,8 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { MatMenuModule } from '@angular/material/menu';
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
import { RouterModule } from '@angular/router';
|
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 { GfLogoModule } from '../logo/logo.module';
|
||||||
import { HeaderComponent } from './header.component';
|
import { HeaderComponent } from './header.component';
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} 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 { primaryColorRgb } from '@ghostfolio/helper';
|
||||||
import {
|
import {
|
||||||
LineController,
|
LineController,
|
||||||
LineElement,
|
LineElement,
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
TimeScale
|
TimeScale
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart } from 'chart.js';
|
||||||
import { primaryColorRgb } from 'libs/helper/src';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-investment-chart',
|
selector: 'gf-investment-chart',
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/helper';
|
||||||
import {
|
import {
|
||||||
Chart,
|
Chart,
|
||||||
Filler,
|
Filler,
|
||||||
@ -18,7 +19,6 @@ import {
|
|||||||
PointElement,
|
PointElement,
|
||||||
TimeScale
|
TimeScale
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import { primaryColorRgb, secondaryColorRgb } from 'libs/helper/src';
|
|
||||||
|
|
||||||
import { LineChartItem } from './interfaces/line-chart.interface';
|
import { LineChartItem } from './interfaces/line-chart.interface';
|
||||||
|
|
||||||
|
@ -5,9 +5,9 @@ import {
|
|||||||
Inject
|
Inject
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { isToday, parse } from 'date-fns';
|
import { isToday, parse } from 'date-fns';
|
||||||
|
|
||||||
import { DataService } from '../../services/data.service';
|
|
||||||
import { LineChartItem } from '../line-chart/interfaces/line-chart.interface';
|
import { LineChartItem } from '../line-chart/interfaces/line-chart.interface';
|
||||||
import { PositionDetailDialogParams } from './interfaces/interfaces';
|
import { PositionDetailDialogParams } from './interfaces/interfaces';
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { GfLineChartModule } from '../../components/line-chart/line-chart.module';
|
|
||||||
import { GfDialogFooterModule } from '../dialog-footer/dialog-footer.module';
|
import { GfDialogFooterModule } from '../dialog-footer/dialog-footer.module';
|
||||||
import { GfDialogHeaderModule } from '../dialog-header/dialog-header.module';
|
import { GfDialogHeaderModule } from '../dialog-header/dialog-header.module';
|
||||||
import { GfFearAndGreedIndexModule } from '../fear-and-greed-index/fear-and-greed-index.module';
|
import { GfFearAndGreedIndexModule } from '../fear-and-greed-index/fear-and-greed-index.module';
|
||||||
|
@ -5,8 +5,8 @@ import {
|
|||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { PortfolioOverview } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-overview.interface';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioOverview } from 'apps/api/src/app/portfolio/interfaces/portfolio-overview.interface';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-portfolio-overview',
|
selector: 'gf-portfolio-overview',
|
||||||
|
@ -7,8 +7,8 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { PortfolioPerformance } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-performance.interface';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioPerformance } from 'apps/api/src/app/portfolio/interfaces/portfolio-performance.interface';
|
|
||||||
import { CountUp } from 'countup.js';
|
import { CountUp } from 'countup.js';
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { PortfolioPerformance } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-performance.interface';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioPerformance } from 'apps/api/src/app/portfolio/interfaces/portfolio-performance.interface';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-portfolio-performance',
|
selector: 'gf-portfolio-performance',
|
||||||
|
@ -5,13 +5,10 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit,
|
OnInit
|
||||||
ViewChild
|
|
||||||
} from '@angular/core';
|
} 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 { Chart } from 'chart.js';
|
|
||||||
import { endOfDay, parseISO, startOfDay } from 'date-fns';
|
import { endOfDay, parseISO, startOfDay } from 'date-fns';
|
||||||
import { primaryColorRgb } from 'libs/helper/src';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-portfolio-positions-chart',
|
selector: 'gf-portfolio-positions-chart',
|
||||||
|
@ -7,8 +7,8 @@ import {
|
|||||||
OnInit,
|
OnInit,
|
||||||
ViewChild
|
ViewChild
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { PortfolioPosition } from '@ghostfolio/api/app/portfolio/interfaces/portfolio-position.interface';
|
||||||
import { Currency } from '@prisma/client';
|
import { Currency } from '@prisma/client';
|
||||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
|
||||||
import { Tooltip } from 'chart.js';
|
import { Tooltip } from 'chart.js';
|
||||||
import { LinearScale } from 'chart.js';
|
import { LinearScale } from 'chart.js';
|
||||||
import { ArcElement } from 'chart.js';
|
import { ArcElement } from 'chart.js';
|
||||||
|
@ -5,15 +5,15 @@ import {
|
|||||||
Inject
|
Inject
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
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 { format, isSameMonth, isToday, parseISO } from 'date-fns';
|
||||||
|
|
||||||
import { DataService } from '../../../services/data.service';
|
|
||||||
import { LineChartItem } from '../../line-chart/interfaces/line-chart.interface';
|
import { LineChartItem } from '../../line-chart/interfaces/line-chart.interface';
|
||||||
import { PositionDetailDialogParams } from './interfaces/interfaces';
|
import { PositionDetailDialogParams } from './interfaces/interfaces';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
host: { class: 'd-flex flex-column h-100' },
|
host: { class: 'd-flex flex-column h-100' },
|
||||||
selector: 'position-detail-dialog',
|
selector: 'gf-position-detail-dialog',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
templateUrl: 'position-detail-dialog.html',
|
templateUrl: 'position-detail-dialog.html',
|
||||||
styleUrls: ['./position-detail-dialog.component.scss']
|
styleUrls: ['./position-detail-dialog.component.scss']
|
||||||
|
@ -2,9 +2,9 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { GfLineChartModule } from '../../../components/line-chart/line-chart.module';
|
|
||||||
import { GfDialogFooterModule } from '../../dialog-footer/dialog-footer.module';
|
import { GfDialogFooterModule } from '../../dialog-footer/dialog-footer.module';
|
||||||
import { GfDialogHeaderModule } from '../../dialog-header/dialog-header.module';
|
import { GfDialogHeaderModule } from '../../dialog-header/dialog-header.module';
|
||||||
import { GfValueModule } from '../../value/value.module';
|
import { GfValueModule } from '../../value/value.module';
|
||||||
@ -26,4 +26,4 @@ import { PositionDetailDialog } from './position-detail-dialog.component';
|
|||||||
providers: [],
|
providers: [],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
})
|
})
|
||||||
export class PositionDetailDialogModule {}
|
export class GfPositionDetailDialogModule {}
|
||||||
|
@ -34,13 +34,15 @@
|
|||||||
<div *ngIf="!isLoading" class="flex-grow-1 text-truncate">
|
<div *ngIf="!isLoading" class="flex-grow-1 text-truncate">
|
||||||
<div class="h6 m-0 text-truncate">{{ position?.name }}</div>
|
<div class="h6 m-0 text-truncate">{{ position?.name }}</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<span>{{ position?.symbol }}</span>
|
<span>{{ position?.symbol | gfSymbol }}</span>
|
||||||
<gf-symbol-icon
|
<gf-symbol-icon
|
||||||
*ngIf="position?.url"
|
*ngIf="position?.url"
|
||||||
class="ml-1"
|
class="ml-1"
|
||||||
[url]="position?.url"
|
[url]="position?.url"
|
||||||
></gf-symbol-icon>
|
></gf-symbol-icon>
|
||||||
<span class="ml-2 text-muted">({{ position?.exchange }})</span>
|
<span *ngIf="position?.exchange" class="ml-2 text-muted"
|
||||||
|
>({{ position.exchange }})</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex mt-1">
|
<div class="d-flex mt-1">
|
||||||
<gf-value
|
<gf-value
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
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 { Subject, Subscription } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@ -2,12 +2,13 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
import { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
||||||
import { GfTrendIndicatorModule } from '../trend-indicator/trend-indicator.module';
|
import { GfTrendIndicatorModule } from '../trend-indicator/trend-indicator.module';
|
||||||
import { GfValueModule } from '../value/value.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';
|
import { PositionComponent } from './position.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -15,12 +16,13 @@ import { PositionComponent } from './position.component';
|
|||||||
exports: [PositionComponent],
|
exports: [PositionComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
GfPositionDetailDialogModule,
|
||||||
GfSymbolIconModule,
|
GfSymbolIconModule,
|
||||||
|
GfSymbolModule,
|
||||||
GfTrendIndicatorModule,
|
GfTrendIndicatorModule,
|
||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
NgxSkeletonLoaderModule,
|
NgxSkeletonLoaderModule,
|
||||||
PositionDetailDialogModule,
|
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
>
|
>
|
||||||
<ng-container matColumnDef="symbol">
|
<ng-container matColumnDef="symbol">
|
||||||
<th mat-header-cell *matHeaderCellDef mat-sort-header i18n>Symbol</th>
|
<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>
|
||||||
|
|
||||||
<ng-container matColumnDef="performance">
|
<ng-container matColumnDef="performance">
|
||||||
|
@ -13,8 +13,8 @@ import { MatPaginator } from '@angular/material/paginator';
|
|||||||
import { MatSort } from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
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 { Order as OrderModel } from '@prisma/client';
|
||||||
import { PortfolioPosition } from 'apps/api/src/app/portfolio/interfaces/portfolio-position.interface';
|
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@ -7,10 +7,11 @@ import { MatPaginatorModule } from '@angular/material/paginator';
|
|||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { GfNoTransactionsInfoModule } from '../no-transactions-info/no-transactions-info.module';
|
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 { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
||||||
import { GfValueModule } from '../value/value.module';
|
import { GfValueModule } from '../value/value.module';
|
||||||
import { PositionsTableComponent } from './positions-table.component';
|
import { PositionsTableComponent } from './positions-table.component';
|
||||||
@ -21,7 +22,9 @@ import { PositionsTableComponent } from './positions-table.component';
|
|||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
GfNoTransactionsInfoModule,
|
GfNoTransactionsInfoModule,
|
||||||
|
GfPositionDetailDialogModule,
|
||||||
GfSymbolIconModule,
|
GfSymbolIconModule,
|
||||||
|
GfSymbolModule,
|
||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
@ -30,7 +33,6 @@ import { PositionsTableComponent } from './positions-table.component';
|
|||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
NgxSkeletonLoaderModule,
|
NgxSkeletonLoaderModule,
|
||||||
PositionDetailDialogModule,
|
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} 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';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-positions',
|
selector: 'gf-positions',
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} 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({
|
@Component({
|
||||||
selector: 'gf-rule',
|
selector: 'gf-rule',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
|
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({
|
@Component({
|
||||||
selector: 'gf-rules',
|
selector: 'gf-rules',
|
||||||
|
@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
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 { GfNoTransactionsInfoModule } from '../no-transactions-info/no-transactions-info.module';
|
||||||
import { GfPositionModule } from '../position/position.module';
|
import { GfPositionModule } from '../position/position.module';
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
<ng-container matColumnDef="symbol">
|
<ng-container matColumnDef="symbol">
|
||||||
<th *matHeaderCellDef i18n mat-header-cell mat-sort-header>Symbol</th>
|
<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>
|
||||||
|
|
||||||
<ng-container matColumnDef="currency">
|
<ng-container matColumnDef="currency">
|
||||||
@ -192,7 +192,7 @@
|
|||||||
<tr
|
<tr
|
||||||
*matRowDef="let row; columns: displayedColumns"
|
*matRowDef="let row; columns: displayedColumns"
|
||||||
mat-row
|
mat-row
|
||||||
(click)="onOpenPositionDialog({ symbol: row.symbol, title: row.symbol })"
|
(click)="onOpenPositionDialog({ symbol: row.symbol, title: '' })"
|
||||||
></tr>
|
></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ import { MatDialog } from '@angular/material/dialog';
|
|||||||
import { MatSort } from '@angular/material/sort';
|
import { MatSort } from '@angular/material/sort';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||||
import { Order as OrderModel } from '@prisma/client';
|
import { Order as OrderModel } from '@prisma/client';
|
||||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@ -55,11 +55,7 @@ export class TransactionsTableComponent
|
|||||||
this.routeQueryParams = route.queryParams
|
this.routeQueryParams = route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
.subscribe((params) => {
|
.subscribe((params) => {
|
||||||
if (
|
if (params['positionDetailDialog'] && params['symbol']) {
|
||||||
params['positionDetailDialog'] &&
|
|
||||||
params['symbol'] &&
|
|
||||||
params['title']
|
|
||||||
) {
|
|
||||||
this.openPositionDialog({
|
this.openPositionDialog({
|
||||||
symbol: params['symbol'],
|
symbol: params['symbol'],
|
||||||
title: params['title']
|
title: params['title']
|
||||||
|
@ -6,9 +6,10 @@ import { MatMenuModule } from '@angular/material/menu';
|
|||||||
import { MatSortModule } from '@angular/material/sort';
|
import { MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableModule } from '@angular/material/table';
|
import { MatTableModule } from '@angular/material/table';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||||
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
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 { GfSymbolIconModule } from '../symbol-icon/symbol-icon.module';
|
||||||
import { GfValueModule } from '../value/value.module';
|
import { GfValueModule } from '../value/value.module';
|
||||||
import { TransactionsTableComponent } from './transactions-table.component';
|
import { TransactionsTableComponent } from './transactions-table.component';
|
||||||
@ -18,7 +19,9 @@ import { TransactionsTableComponent } from './transactions-table.component';
|
|||||||
exports: [TransactionsTableComponent],
|
exports: [TransactionsTableComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
GfPositionDetailDialogModule,
|
||||||
GfSymbolIconModule,
|
GfSymbolIconModule,
|
||||||
|
GfSymbolModule,
|
||||||
GfValueModule,
|
GfValueModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
@ -26,7 +29,6 @@ import { TransactionsTableComponent } from './transactions-table.component';
|
|||||||
MatSortModule,
|
MatSortModule,
|
||||||
MatTableModule,
|
MatTableModule,
|
||||||
NgxSkeletonLoaderModule,
|
NgxSkeletonLoaderModule,
|
||||||
PositionDetailDialogModule,
|
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -5,8 +5,8 @@ import {
|
|||||||
OnChanges,
|
OnChanges,
|
||||||
OnInit
|
OnInit
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||||
import { format, isDate } from 'date-fns';
|
import { format, isDate } from 'date-fns';
|
||||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -45,21 +45,7 @@ export class AuthInterceptor implements HttpInterceptor {
|
|||||||
authReq = req.clone({ headers });
|
authReq = req.clone({ headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
return next.handle(authReq).pipe(
|
return next.handle(authReq);
|
||||||
tap(
|
|
||||||
() => {},
|
|
||||||
(err: any) => {
|
|
||||||
if (err instanceof HttpErrorResponse) {
|
|
||||||
if (err.status !== 401) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tokenStorageService.signOut();
|
|
||||||
this.router.navigate(['start']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,15 +15,22 @@ import {
|
|||||||
MatSnackBarRef,
|
MatSnackBarRef,
|
||||||
TextOnlySnackBar
|
TextOnlySnackBar
|
||||||
} from '@angular/material/snack-bar';
|
} from '@angular/material/snack-bar';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
import { Observable, throwError } from 'rxjs';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { catchError, tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { TokenStorageService } from '../services/token-storage.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HttpResponseInterceptor implements HttpInterceptor {
|
export class HttpResponseInterceptor implements HttpInterceptor {
|
||||||
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;
|
public snackBarRef: MatSnackBarRef<TextOnlySnackBar>;
|
||||||
|
|
||||||
public constructor(private snackBar: MatSnackBar) {}
|
public constructor(
|
||||||
|
private router: Router,
|
||||||
|
private tokenStorageService: TokenStorageService,
|
||||||
|
private snackBar: MatSnackBar
|
||||||
|
) {}
|
||||||
|
|
||||||
public intercept(
|
public intercept(
|
||||||
request: HttpRequest<any>,
|
request: HttpRequest<any>,
|
||||||
@ -70,6 +77,9 @@ export class HttpResponseInterceptor implements HttpInterceptor {
|
|||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (error.status === StatusCodes.UNAUTHORIZED) {
|
||||||
|
this.tokenStorageService.signOut();
|
||||||
|
this.router.navigate(['start']);
|
||||||
}
|
}
|
||||||
|
|
||||||
return throwError('');
|
return throwError('');
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||||
import { environment } from 'apps/client/src/environments/environment';
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
import { baseCurrency } from 'libs/helper/src';
|
import { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||||
|
import { baseCurrency } from '@ghostfolio/helper';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DataService } from '../../services/data.service';
|
import { environment } from '../../../environments/environment';
|
||||||
import { TokenStorageService } from '../../services/token-storage.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-about-page',
|
selector: 'gf-about-page',
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<a href="https://dotsilver.ch">Thomas Kaul</a>.
|
<a href="https://dotsilver.ch">Thomas Kaul</a>.
|
||||||
<ng-container *ngIf="lastPublish">
|
<ng-container *ngIf="lastPublish">
|
||||||
This instance has been last published on {{ lastPublish
|
This instance has been last published on {{ lastPublish
|
||||||
}}</ng-container
|
}}.</ng-container
|
||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
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';
|
import { AccountPageComponent } from './account-page.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Access } from 'apps/api/src/app/access/interfaces/access.interface';
|
import { Access } from '@ghostfolio/api/app/access/interfaces/access.interface';
|
||||||
import { User } from 'apps/api/src/app/user/interfaces/user.interface';
|
import { User } from '@ghostfolio/api/app/user/interfaces/user.interface';
|
||||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
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 { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { DataService } from '../../services/data.service';
|
|
||||||
import { TokenStorageService } from '../../services/token-storage.service';
|
|
||||||
import { Currency } from '.prisma/client';
|
import { Currency } from '.prisma/client';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -4,8 +4,8 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
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 { AccountPageRoutingModule } from './account-page-routing.module';
|
||||||
import { AccountPageComponent } from './account-page.component';
|
import { AccountPageComponent } from './account-page.component';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
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';
|
import { AdminPageComponent } from './admin-page.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
import { AdminData } from 'apps/api/src/app/admin/interfaces/admin-data.interface';
|
import { AdminData } from '@ghostfolio/api/app/admin/interfaces/admin-data.interface';
|
||||||
|
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||||
|
import { CacheService } from '@ghostfolio/client/services/cache.service';
|
||||||
|
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||||
|
import { DEFAULT_DATE_FORMAT } from '@ghostfolio/helper';
|
||||||
import { formatDistanceToNow, isValid, parseISO, sub } from 'date-fns';
|
import { formatDistanceToNow, isValid, parseISO, sub } from 'date-fns';
|
||||||
import { DEFAULT_DATE_FORMAT } from 'libs/helper/src';
|
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { AdminService } from '../../services/admin.service';
|
|
||||||
import { CacheService } from '../../services/cache.service';
|
|
||||||
import { DataService } from '../../services/data.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-admin-page',
|
selector: 'gf-admin-page',
|
||||||
templateUrl: './admin-page.html',
|
templateUrl: './admin-page.html',
|
||||||
@ -97,6 +96,7 @@ export class AdminPageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public formatDistanceToNow(aDateString: string) {
|
public formatDistanceToNow(aDateString: string) {
|
||||||
|
if (aDateString) {
|
||||||
const distanceString = formatDistanceToNow(
|
const distanceString = formatDistanceToNow(
|
||||||
sub(parseISO(aDateString), { seconds: 10 }),
|
sub(parseISO(aDateString), { seconds: 10 }),
|
||||||
{
|
{
|
||||||
@ -109,6 +109,9 @@ export class AdminPageComponent implements OnInit {
|
|||||||
: distanceString;
|
: distanceString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
>In Progress</ng-container
|
>In Progress</ng-container
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="mt-2 overflow-hidden">
|
||||||
<button
|
<button
|
||||||
class="mb-2 mr-2"
|
class="mb-2 mr-2 mw-100"
|
||||||
color="accent"
|
color="accent"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
(click)="onFlushCache()"
|
(click)="onFlushCache()"
|
||||||
@ -37,6 +37,7 @@
|
|||||||
<span i18n>Reset Data Gathering</span>
|
<span i18n>Reset Data Gathering</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
class="mw-100"
|
||||||
color="warn"
|
color="warn"
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
[disabled]="dataGatheringInProgress"
|
[disabled]="dataGatheringInProgress"
|
||||||
@ -99,13 +100,13 @@
|
|||||||
{{ userItem.createdAt | date: defaultDateFormat }}
|
{{ userItem.createdAt | date: defaultDateFormat }}
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell pr-2 py-2 text-truncate">
|
<td class="mat-cell pr-2 py-2 text-truncate">
|
||||||
{{ userItem._count.Order }}
|
{{ userItem._count?.Order }}
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell pr-2 py-2 text-truncate">
|
<td class="mat-cell pr-2 py-2 text-truncate">
|
||||||
{{ userItem.Analytics.activityCount }}
|
{{ userItem.Analytics?.activityCount }}
|
||||||
</td>
|
</td>
|
||||||
<td class="mat-cell pr-2 py-2 text-truncate">
|
<td class="mat-cell pr-2 py-2 text-truncate">
|
||||||
{{ formatDistanceToNow(userItem.Analytics.updatedAt) }}
|
{{ formatDistanceToNow(userItem.Analytics?.updatedAt) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -2,8 +2,8 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { CacheService } from '@ghostfolio/client/services/cache.service';
|
||||||
|
|
||||||
import { CacheService } from '../../services/cache.service';
|
|
||||||
import { AdminPageRoutingModule } from './admin-page-routing.module';
|
import { AdminPageRoutingModule } from './admin-page-routing.module';
|
||||||
import { AdminPageComponent } from './admin-page.component';
|
import { AdminPageComponent } from './admin-page.component';
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user