Compare commits

..

16 Commits

Author SHA1 Message Date
2aff139982 Release 0.89.0 (#35) 2021-04-21 21:17:25 +02:00
6c7adb6193 Prettify generic scraper symbols (#34) 2021-04-21 21:15:49 +02:00
bb2cd1c85a Improve documentation (#31) 2021-04-21 21:14:06 +02:00
9d92c48ab7 Fix text truncation of buttons (#33) 2021-04-21 21:13:38 +02:00
dbed4ea527 Feature/improve imports with paths in tsconfig (#32)
* Improve imports
2021-04-21 20:27:39 +02:00
8d149b5e2b Reduce period of max data gathering (#30) 2021-04-21 18:58:21 +02:00
27f1ec5d8a Release 0.88.0 (#29) 2021-04-20 21:55:57 +02:00
c361143ba2 Fix interceptor for unauthorized http response (#27) 2021-04-20 21:54:01 +02:00
069006145a Improve scraper (#28) 2021-04-20 21:52:01 +02:00
3e3395aff9 Release 0.87.0 (#26) 2021-04-19 22:27:50 +02:00
a2687eacbc Feature/implement scraper (#25)
* Clean up imports

* Implement scraper

* Sort imports
2021-04-19 22:25:52 +02:00
0f2c8c856c Clean up imports (#24) 2021-04-19 14:38:55 +02:00
ec4dbf2a51 Feature/extend database seed (#23)
* Extend database seed

* platforms
* transactions of demo user

* Improve error handling
2021-04-19 14:14:16 +02:00
3d34aa5e80 Add guards (#22) 2021-04-18 20:49:57 +02:00
c45bd70711 Harmonize scripts (#21) 2021-04-18 20:35:34 +02:00
9f876e6020 Update README.md (#20) 2021-04-18 20:30:58 +02:00
127 changed files with 1016 additions and 486 deletions

View File

@ -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

View File

@ -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).

View File

@ -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';

View File

@ -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';

View File

@ -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()

View File

@ -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';

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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')

View File

@ -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';

View File

@ -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';

View File

@ -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()

View File

@ -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()

View File

@ -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';

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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()

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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,

View File

@ -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;
}
}
} }

View File

@ -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({

View File

@ -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(

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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('');
}
} }

View File

@ -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';

View File

@ -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 {

View File

@ -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';

View File

@ -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');

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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' }),

View File

@ -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
]; ];

View File

@ -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(

View File

@ -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 {};
}
}

View File

@ -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';

View File

@ -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';

View File

@ -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];

View File

@ -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;

View File

@ -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: {

View File

@ -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'
} }
) )
], ],

View File

@ -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';

View File

@ -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',

View File

@ -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',

View File

@ -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,

View File

@ -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';

View File

@ -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',

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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',

View File

@ -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';

View File

@ -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',

View File

@ -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',

View File

@ -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';

View File

@ -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']

View File

@ -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 {}

View File

@ -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

View File

@ -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';

View File

@ -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: [],

View File

@ -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">

View File

@ -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';

View File

@ -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: [],

View File

@ -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',

View File

@ -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',

View File

@ -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',

View File

@ -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';

View File

@ -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>

View File

@ -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']

View File

@ -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: [],

View File

@ -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({

View File

@ -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']);
}
}
)
);
} }
} }

View File

@ -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('');

View File

@ -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',

View File

@ -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>

View File

@ -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 = [

View File

@ -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({

View File

@ -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';

View File

@ -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 = [

View File

@ -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();

View File

@ -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>

View File

@ -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