Compare commits

...

4 Commits

Author SHA1 Message Date
7f3f75386d Release 1.69.0 (#454) 2021-11-07 09:45:19 +01:00
678544748a Add symbol mapping (#452)
* Add symbol mapping

* Update changelog
2021-11-07 09:42:36 +01:00
632f3e3872 Add ok.csv (#453) 2021-11-06 21:09:06 +01:00
87301ddbd5 Feature/improve registration page (#451)
* Improve registration page

* Update changelog
2021-11-02 21:49:57 +01:00
20 changed files with 169 additions and 49 deletions

View File

@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 1.69.0 - 07.11.2021
### Added
- Added the symbol mapping attribute to the symbol profile model
### Changed
- Improved the registration page
### Todo
- Apply data migration (`yarn database:migrate`)
## 1.68.0 - 01.11.2021
### Changed
@ -60,7 +74,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.62.0 - 17.10.2021
@ -158,7 +172,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.55.0 - 20.09.2021
@ -173,7 +187,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.54.0 - 18.09.2021
@ -194,7 +208,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.53.0 - 13.09.2021
@ -316,7 +330,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.41.0 - 21.08.2021
@ -369,7 +383,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.38.0 - 14.08.2021
@ -429,7 +443,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Todo
- Apply data migration (`yarn prisma migrate deploy`)
- Apply data migration (`yarn database:migrate`)
## 1.34.0 - 07.08.2021

View File

@ -101,7 +101,7 @@ docker-compose -f docker/docker-compose-build-local.yml up
Run the following command to setup the database once Ghostfolio is running:
```bash
docker-compose -f docker/docker-compose-build-local.yml exec ghostfolio yarn setup:database
docker-compose -f docker/docker-compose-build-local.yml exec ghostfolio yarn database:setup
```
### Fetch Historical Data
@ -112,6 +112,14 @@ Open http://localhost:3333 in your browser and accomplish these steps:
1. Go to the _Admin Control Panel_ and click _Gather All Data_ to fetch historical data
1. Click _Sign out_ and check out the _Live Demo_
### Migrate Database
With the following command you can keep your database schema in sync after a Ghostfolio version update:
```bash
docker-compose -f docker/docker-compose-build-local.yml exec ghostfolio yarn database:migrate
```
## Development
### Prerequisites
@ -126,7 +134,7 @@ Open http://localhost:3333 in your browser and accomplish these steps:
1. Run `cd docker`
1. Run `docker compose up -d` to start [PostgreSQL](https://www.postgresql.org) and [Redis](https://redis.io)
1. Run `cd -` to go back to the project root directory
1. Run `yarn setup:database` to initialize the database schema and populate your database with (example) data
1. Run `yarn database:setup` to initialize the database schema and populate your database with (example) data
1. Start server and client (see [_Development_](#Development))
1. Login as _Admin_ with the following _Security Token_: `ae76872ae8f3419c6d6f64bf51888ecbcc703927a342d815fafe486acdb938da07d0cf44fca211a0be74a423238f535362d390a41e81e633a9ce668a6e31cdf9`
1. Go to the _Admin Control Panel_ and click _Gather All Data_ to fetch historical data

View File

@ -6,6 +6,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.se
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
import { Module } from '@nestjs/common';
import { CacheController } from './cache.controller';
@ -15,7 +16,8 @@ import { CacheController } from './cache.controller';
DataGatheringModule,
DataProviderModule,
ExchangeRateDataModule,
RedisCacheModule
RedisCacheModule,
SymbolProfileModule
],
controllers: [CacheController],
providers: [

View File

@ -4,6 +4,7 @@ import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.se
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
@ -18,7 +19,8 @@ import { InfoService } from './info.service';
JwtModule.register({
secret: process.env.JWT_SECRET_KEY,
signOptions: { expiresIn: '30 days' }
})
}),
SymbolProfileModule
],
controllers: [InfoController],
providers: [

View File

@ -8,7 +8,7 @@ import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-
import { ExchangeRateDataModule } from '@ghostfolio/api/services/exchange-rate-data.module';
import { ImpersonationModule } from '@ghostfolio/api/services/impersonation.module';
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile.module';
import { Module } from '@nestjs/common';
import { CurrentRateService } from './current-rate.service';
@ -27,6 +27,7 @@ import { RulesService } from './rules.service';
ImpersonationModule,
OrderModule,
PrismaModule,
SymbolProfileModule,
UserModule
],
controllers: [PortfolioController],
@ -35,8 +36,7 @@ import { RulesService } from './rules.service';
CurrentRateService,
MarketDataService,
PortfolioService,
RulesService,
SymbolProfileService
RulesService
]
})
export class PortfolioModule {}

View File

@ -6,6 +6,7 @@ import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { Module } from '@nestjs/common';
import { ExchangeRateDataModule } from './exchange-rate-data.module';
import { SymbolProfileModule } from './symbol-profile.module';
@Module({
imports: [
@ -13,7 +14,8 @@ import { ExchangeRateDataModule } from './exchange-rate-data.module';
DataEnhancerModule,
DataProviderModule,
ExchangeRateDataModule,
PrismaModule
PrismaModule,
SymbolProfileModule
],
providers: [DataGatheringService],
exports: [DataEnhancerModule, DataGatheringService]

View File

@ -1,3 +1,4 @@
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile.service';
import {
benchmarks,
ghostfolioFearAndGreedIndexSymbol
@ -32,7 +33,8 @@ export class DataGatheringService {
private readonly dataProviderService: DataProviderService,
private readonly exchangeRateDataService: ExchangeRateDataService,
private readonly ghostfolioScraperApi: GhostfolioScraperApiService,
private readonly prismaService: PrismaService
private readonly prismaService: PrismaService,
private readonly symbolProfileService: SymbolProfileService
) {}
public async gather7Days() {
@ -132,13 +134,22 @@ export class DataGatheringService {
}
const currentData = await this.dataProviderService.get(dataGatheringItems);
const symbolProfiles = await this.symbolProfileService.getSymbolProfiles(
dataGatheringItems.map(({ symbol }) => {
return symbol;
})
);
for (const [symbol, response] of Object.entries(currentData)) {
const symbolMapping = symbolProfiles.find((symbolProfile) => {
return symbolProfile.symbol === symbol;
})?.symbolMapping;
for (const dataEnhancer of this.dataEnhancers) {
try {
currentData[symbol] = await dataEnhancer.enhance({
response,
symbol
symbol: symbolMapping[dataEnhancer.getName()] ?? symbol
});
} catch (error) {
console.error(`Failed to enhance data for symbol ${symbol}`, error);

View File

@ -70,4 +70,8 @@ export class TrackinsightDataEnhancerService implements DataEnhancerInterface {
return Promise.resolve(response);
}
public getName() {
return 'TRACKINSIGHT';
}
}

View File

@ -8,4 +8,6 @@ export interface DataEnhancerInterface {
response: IDataProviderResponse;
symbol: string;
}): Promise<IDataProviderResponse>;
getName(): string;
}

View File

@ -5,13 +5,14 @@ import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
export interface EnhancedSymbolProfile {
assetClass: AssetClass;
assetSubClass: AssetSubClass;
countries: Country[];
createdAt: Date;
currency: string | null;
dataSource: DataSource;
id: string;
name: string | null;
updatedAt: Date;
symbol: string;
countries: Country[];
sectors: Sector[];
symbol: string;
symbolMapping?: { [key: string]: string };
updatedAt: Date;
}

View File

@ -0,0 +1,11 @@
import { PrismaModule } from '@ghostfolio/api/services/prisma.module';
import { Module } from '@nestjs/common';
import { SymbolProfileService } from './symbol-profile.service';
@Module({
imports: [PrismaModule],
providers: [SymbolProfileService],
exports: [SymbolProfileService]
})
export class SymbolProfileModule {}

View File

@ -29,7 +29,8 @@ export class SymbolProfileService {
return symbolProfiles.map((symbolProfile) => ({
...symbolProfile,
countries: this.getCountries(symbolProfile),
sectors: this.getSectors(symbolProfile)
sectors: this.getSectors(symbolProfile),
symbolMapping: this.getSymbolMapping(symbolProfile)
}));
}
@ -61,4 +62,12 @@ export class SymbolProfileService {
}
);
}
private getSymbolMapping(symbolProfile: SymbolProfile) {
return (
(symbolProfile['symbolMapping'] as {
[key: string]: string;
}) ?? {}
);
}
}

View File

@ -229,7 +229,13 @@
mat-button
[routerLink]="['/']"
>
<gf-logo [hideName]="!currentRoute || currentRoute === 'start'"></gf-logo>
<gf-logo
[hideName]="
!currentRoute ||
currentRoute === 'register' ||
currentRoute === 'start'
"
></gf-logo>
</a>
<span class="spacer"></span>
<a

View File

@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { DataService } from '@ghostfolio/client/services/data.service';
@ -6,6 +6,7 @@ import { TokenStorageService } from '@ghostfolio/client/services/token-storage.s
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { format } from 'date-fns';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -20,6 +21,7 @@ import { ShowAccessTokenDialog } from './show-access-token-dialog/show-access-to
export class RegisterPageComponent implements OnDestroy, OnInit {
public currentYear = format(new Date(), 'yyyy');
public demoAuthToken: string;
public deviceType: string;
public hasPermissionForSocialLogin: boolean;
public historicalDataItems: LineChartItem[];
@ -29,8 +31,8 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
* @constructor
*/
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private dialog: MatDialog,
private router: Router,
private tokenStorageService: TokenStorageService
@ -45,6 +47,7 @@ export class RegisterPageComponent implements OnDestroy, OnInit {
const { demoAuthToken, globalPermissions } = this.dataService.fetchInfo();
this.demoAuthToken = demoAuthToken;
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.hasPermissionForSocialLogin = hasPermission(
globalPermissions,
permissions.enableSocialLogin

View File

@ -1,30 +1,46 @@
<div class="intro-container mb-5">
<div class="intro-inner-container mx-auto">
<div class="h-100 intro w-100"></div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col">
<h3 class="d-flex justify-content-center mb-3 text-center" i18n>
Create your Account
</h3>
<mat-card class="mb-4">
<mat-card-content class="text-center">
<button
class="d-inline-block"
color="primary"
<div
class="align-items-center d-flex flex-column justify-content-center w-100"
>
<gf-logo size="large"></gf-logo>
<p class="lead m-0">Wealth Management Software</p>
</div>
</div>
<div class="button-container row">
<div class="align-items-center col d-flex justify-content-center">
<div class="py-5 text-center">
<button
class="d-inline-block"
color="primary"
i18n
mat-flat-button
[disabled]="!demoAuthToken"
(click)="createAccount()"
>
Create Account
</button>
<ng-container *ngIf="hasPermissionForSocialLogin">
<div
class="m-3 text-muted"
i18n
mat-flat-button
[disabled]="!demoAuthToken"
(click)="createAccount()"
[ngClass]="{'d-inline': deviceType !== 'mobile' }"
>
Create Account
</button>
<ng-container *ngIf="hasPermissionForSocialLogin">
<div class="my-3 text-muted" i18n>or</div>
<a color="accent" href="/api/auth/google" mat-flat-button
><ion-icon class="mr-1" name="logo-google"></ion-icon
><span i18n>Continue with Google</span></a
>
</ng-container>
</mat-card-content>
</mat-card>
or
</div>
<a color="accent" href="/api/auth/google" mat-flat-button
><ion-icon class="mr-1" name="logo-google"></ion-icon
><span i18n>Continue with Google</span></a
>
</ng-container>
</div>
</div>
</div>
</div>

View File

@ -1,3 +1,26 @@
:host {
display: block;
.button-container {
.mat-stroked-button {
background-color: var(--light-background);
}
}
.intro-container {
background-color: #ffffff;
margin-top: -5rem;
.intro-inner-container {
aspect-ratio: 16 / 9;
max-height: 66vh;
.intro {
background-image: url('/assets/intro.jpg');
background-position: top left;
background-repeat: no-repeat;
background-size: contain;
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "ghostfolio",
"version": "1.68.0",
"version": "1.69.0",
"homepage": "https://ghostfol.io",
"license": "AGPL-3.0",
"scripts": {
@ -19,8 +19,10 @@
"database:format-schema": "prisma format",
"database:generate-typings": "prisma generate",
"database:gui": "prisma studio",
"database:migrate": "prisma migrate deploy",
"database:push": "prisma db push",
"database:seed": "prisma db seed --preview-feature",
"database:setup": "yarn database:push && yarn database:seed",
"dep-graph": "nx dep-graph",
"e2e": "ng e2e",
"format": "nx format:write",
@ -33,7 +35,6 @@
"nx": "nx",
"postinstall": "prisma generate && ngcc --properties es2015 browser module main",
"replace-placeholders-in-build": "node ./replace.build.js",
"setup:database": "yarn database:push && yarn database:seed",
"start": "node dist/apps/api/main",
"start:client": "ng serve client --hmr -o",
"start:prod": "node apps/api/main",

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "SymbolProfile" ADD COLUMN "symbolMapping" JSONB;

View File

@ -130,6 +130,7 @@ model SymbolProfile {
updatedAt DateTime @updatedAt
sectors Json?
symbol String
symbolMapping Json?
@@unique([dataSource, symbol])
}

2
test/import/ok.csv Normal file
View File

@ -0,0 +1,2 @@
Date,Code,Currency,Price,Quantity,Action,Fee
16/09/2021,MSFT,USD,298.580,5,buy,19.00
1 Date Code Currency Price Quantity Action Fee
2 16/09/2021 MSFT USD 298.580 5 buy 19.00