Browse Source

Refactoring

pull/830/head
Thomas 3 years ago
parent
commit
96a16b0e38
  1. 19
      .env
  2. 11
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts
  3. 75
      apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts
  4. 59
      apps/client/src/app/services/ics/ics.service.ts
  5. 26
      libs/common/src/lib/helper.ts

19
.env

@ -12,5 +12,24 @@ POSTGRES_PASSWORD=password
ACCESS_TOKEN_SALT=GHOSTFOLIO ACCESS_TOKEN_SALT=GHOSTFOLIO
ALPHA_VANTAGE_API_KEY= ALPHA_VANTAGE_API_KEY=
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:5432/${POSTGRES_DB}?sslmode=prefer
DATA_SOURCES=["GHOSTFOLIO","GOOGLE_SHEETS","YAHOO"]
ENABLE_FEATURE_BLOG=true
ENABLE_FEATURE_CUSTOM_SYMBOLS=true
ENABLE_FEATURE_FEAR_AND_GREED_INDEX=true
ENABLE_FEATURE_SOCIAL_LOGIN=true
ENABLE_FEATURE_STATISTICS=true
ENABLE_FEATURE_SUBSCRIPTION=true
ENABLE_FEATURE_SYSTEM_MESSAGE=true
GOOGLE_CLIENT_ID=43371500610-uqv0gt8h5l6v2uo69c53o0h1ssfkbbni.apps.googleusercontent.com
GOOGLE_SECRET=0bOB89maN5JA43vqXkzvuHDs
GOOGLE_SHEETS_ACCOUNT=market-data@ghostfolio-337512.iam.gserviceaccount.com
GOOGLE_SHEETS_ID=1sSAsqhtleACpj0BQIrMz48TGGCXYM5RO80SeHtz_Seg
GOOGLE_SHEETS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQChsFi+CYhF3CFI\nfccrucx1ZIXY3ZEVQ2R+gIhUsoGBkxoDU/oreuFB/xTYBSvIbVsub9KpLLRFKboi\nuRRs4UWodJGUZzHxYnUN5lIFA5V6hNVCzd137QKjJM7FatSiGEM/OcNqOKNkOCUs\nZlHQIkzHXtmAAA4JrpEQzNe0qdYMcYhoWG079/CvbBVx3y/90eX8ue2C1l1fDm1J\nB3yJj3GgiClH+gMRvP6om4bp+dZJ8CYDje2fTfnlZ99MxK6rQxWu/Eou6Ws8S6sf\nMhD8nCTSlG822Fuk8RLrzxHlvCFKAFEztLtCo7v56v3iTMW5sOplQa78/T7Mlpzh\njYq7YAJlAgMBAAECggEAIselaylvQgG+OhLuMSJsD9Nx9CqC7xU6THjW+osUTvxG\nw/EANvKdej4FrIr+NkSJsNU2dhQK6fa2FoqD0YDqpDgA6bCB/neskLMasP/qmzpw\nCkjwqv+VSeUcwjv+5ag87OB+v75TrTbjjierUQ9Tvy4QsJcybdQ6WagKfU7sH2xD\n8mNV4gVEYzBbWfG29v1gIdVmxKdZbfkCFB1hcvv8ZFwNq9NBIT8tbLEFfnrD9tw1\nYiK6y/efSfZ9OZ+2kIBntI0MR9jysg7tD+sDHC9Q+O8XPzwi/x4yLdFyI8uTivYA\nq/GdvnDXmH0WfuVGP8Hs059TbW7xNaMDX3cczVu4gQKBgQDTE3K4jYFE+NgXnCjD\nXUqEuOwFtEX12isA4SvrxZzw9CM7VR3yG/i43/O6B2roX0niVHPTi/n+cy2bUPy6\nXt5pO+qR6n4OsH/BpvP2vj7RNb4OaCbVstQQGvpOrNrXc678F+LwnquHek17rbV9\nXqVLYbbNd7iZNk468ZE9GeMV5QKBgQDEGgX76j8mSDH7zfdh/IO1oWOY91NdL1uh\nOsp4vPv4dhbMIPDebSyE316Zq6JwNGbD2RW3sUSRtL21z2jGzN0aqSTRUNTzFZIr\n5CLmIrxxjsoy6YkQZOx4OL4QWulj+inzdgpDsPA++HbAeGl7SJi9/2wLiqn2BNWL\nukBsjEpygQKBgGPj9Uu/s+iXN3Tc8zGZqdVryk7cxKsX53gQGAAJUj952l6O5pAY\nirm7SpXEQuTbi5Sv0OzRdqrjiTbSuffdQ7Zbo6QQbD25a4yS3SvtVr8dhuc8hPxn\nGBLTIZgwF5UU6z/kcgLbpGOGDrs0NwqwytsE0EUmnlbrq1Qb1FctNBm9AoGAGeLB\nhXZvbZM8HdwbWrDlhfVO22NSesuEkezby0JPFIYqDjoO8Z2Bsex2ZVyVrbANHK8s\nQbpBreYo4LYHQ67JRPqs5ICCC7B+QhL0VGKjc24A3OWc9TANUvVSiYAmrM7Z+MxN\nIJBbtkRAELoUWnTDzNjJn2BnfRU4RyCH3oxKS4ECgYEAgDj5jWTpFj7jX6L2OKgU\ntWWnZwW3v9PVEKFM0UTalIEL0FA99TUwJ2dxkdSagNHftVNIOj2SbKdXK+QLgVRn\naGO85QZ2IDgiOiPfuqUfmPsnk6WsZRhRSbUzdzel/ZfgLCbpErStFfnuPDCrIz7W\nIQf4jR6u3q8L6cmnKa6e0u0=\n-----END PRIVATE KEY-----\n"
IS_DEVELOPMENT_MODE=false
JWT_SECRET_KEY=123456 JWT_SECRET_KEY=123456
#MAX_ORDERS_TO_IMPORT=20
PORT=3333 PORT=3333
RAKUTEN_RAPID_API_KEY=db994bceaamsh39c922e3ac79677p17da8ajsnd69256103cbd
STRIPE_PUBLIC_KEY=pk_test_0OKelyHqlD6IHLq7cVuSCYWR
STRIPE_SECRET_KEY=sk_test_uZgtp2vZNugwkfALI0LQhGn8
WEB_AUTH_RP_ID=ghostfol.io

11
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts

@ -211,15 +211,14 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
) )
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((data) => { .subscribe((data) => {
downloadAsFile( downloadAsFile({
data, content: data,
`ghostfolio-export-${this.SymbolProfile?.symbol}-${format( fileName: `ghostfolio-export-${this.SymbolProfile?.symbol}-${format(
parseISO(data.meta.date), parseISO(data.meta.date),
'yyyyMMddHHmm' 'yyyyMMddHHmm'
)}.json`, )}.json`,
'text/plain', format: 'json'
'json' });
);
}); });
} }

75
apps/client/src/app/pages/portfolio/transactions/transactions-page.component.ts

@ -7,15 +7,16 @@ import { Activity } from '@ghostfolio/api/app/order/interfaces/activities.interf
import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto'; import { UpdateOrderDto } from '@ghostfolio/api/app/order/update-order.dto';
import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component'; import { PositionDetailDialog } from '@ghostfolio/client/components/position/position-detail-dialog/position-detail-dialog.component';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { IcsService } from '@ghostfolio/client/services/ics/ics.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { ImportTransactionsService } from '@ghostfolio/client/services/import-transactions.service'; import { ImportTransactionsService } from '@ghostfolio/client/services/import-transactions.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { downloadAsFile } from '@ghostfolio/common/helper'; import { downloadAsFile } from '@ghostfolio/common/helper';
import { Export, User } from '@ghostfolio/common/interfaces'; import { User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DataSource, Order as OrderModel, Type } from '@prisma/client'; import { DataSource, Order as OrderModel } from '@prisma/client';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
import { capitalize, isArray } from 'lodash'; import { isArray } from 'lodash';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -50,6 +51,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
private dataService: DataService, private dataService: DataService,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private dialog: MatDialog, private dialog: MatDialog,
private icsService: IcsService,
private impersonationStorageService: ImpersonationStorageService, private impersonationStorageService: ImpersonationStorageService,
private importTransactionsService: ImportTransactionsService, private importTransactionsService: ImportTransactionsService,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -156,15 +158,14 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
delete activity.id; delete activity.id;
} }
downloadAsFile( downloadAsFile({
data, content: data,
`ghostfolio-export-${format( fileName: `ghostfolio-export-${format(
parseISO(data.meta.date), parseISO(data.meta.date),
'yyyyMMddHHmm' 'yyyyMMddHHmm'
)}.json`, )}.json`,
'text/plain', format: 'json'
'json' });
);
}); });
} }
@ -173,15 +174,16 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
.fetchExport(activityIds) .fetchExport(activityIds)
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((data) => { .subscribe((data) => {
downloadAsFile( downloadAsFile({
this.getIcsContent(data.activities), content: this.icsService.transformActivitiesToIcsContent(
`ghostfolio-drafts-${format( data.activities
),
fileName: `ghostfolio-drafts-${format(
parseISO(data.meta.date), parseISO(data.meta.date),
'yyyyMMddHHmm' 'yyyyMMddHHmm'
)}.ics`, )}.ics`,
'text/plain', format: 'string'
'string' });
);
}); });
} }
@ -315,49 +317,6 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private getIcsContent(aActivities: Export['activities']) {
const header = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Ghostfolio//NONSGML v1.0//EN'
];
const events = aActivities.map((activity) => {
return this.getEvent({
date: parseISO(activity.date),
id: activity.id,
symbol: activity.symbol,
type: activity.type
});
});
const footer = ['END:VCALENDAR'];
return [...header, ...events, ...footer].join('\n');
}
private getEvent({
date,
id,
symbol,
type
}: {
date: Date;
id: string;
symbol: string;
type: Type;
}) {
const today = format(new Date(), 'yyyyMMdd');
return [
'BEGIN:VEVENT',
`UID:${id}`,
`DTSTAMP:${today}T000000`,
`DTSTART;VALUE=DATE:${format(date, 'yyyyMMdd')}`,
`DTEND;VALUE=DATE:${format(date, 'yyyyMMdd')}`,
`SUMMARY:${capitalize(type)} ${symbol}`,
'END:VEVENT'
].join('\n');
}
private handleImportError({ private handleImportError({
activities, activities,
error error

59
apps/client/src/app/services/ics/ics.service.ts

@ -0,0 +1,59 @@
import { Injectable } from '@angular/core';
import { capitalize } from '@ghostfolio/common/helper';
import { Export } from '@ghostfolio/common/interfaces';
import { Type } from '@prisma/client';
import { format, parseISO } from 'date-fns';
@Injectable({
providedIn: 'root'
})
export class IcsService {
private readonly ICS_DATE_FORMAT = 'yyyyMMdd';
public constructor() {}
public transformActivitiesToIcsContent(
aActivities: Export['activities']
): string {
const header = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//Ghostfolio//NONSGML v1.0//EN'
];
const events = aActivities.map((activity) => {
return this.getEvent({
date: parseISO(activity.date),
id: activity.id,
symbol: activity.symbol,
type: activity.type
});
});
const footer = ['END:VCALENDAR'];
return [...header, ...events, ...footer].join('\n');
}
private getEvent({
date,
id,
symbol,
type
}: {
date: Date;
id: string;
symbol: string;
type: Type;
}) {
const today = format(new Date(), this.ICS_DATE_FORMAT);
return [
'BEGIN:VEVENT',
`UID:${id}`,
`DTSTAMP:${today}T000000`,
`DTSTART;VALUE=DATE:${format(date, this.ICS_DATE_FORMAT)}`,
`DTEND;VALUE=DATE:${format(date, this.ICS_DATE_FORMAT)}`,
`SUMMARY:${capitalize(type)} ${symbol}`,
'END:VEVENT'
].join('\n');
}
}

26
libs/common/src/lib/helper.ts

@ -12,24 +12,28 @@ export function decodeDataSource(encodedDataSource: string) {
return Buffer.from(encodedDataSource, 'hex').toString(); return Buffer.from(encodedDataSource, 'hex').toString();
} }
export function downloadAsFile( export function downloadAsFile({
aContent: unknown, content,
aFileName: string, contentType = 'text/plain',
aContentType: string, fileName,
aType: 'json' | 'string' format
) { }: {
content: unknown;
contentType?: string;
fileName: string;
format: 'json' | 'string';
}) {
const a = document.createElement('a'); const a = document.createElement('a');
let content = aContent;
if (aType === 'json') { if (format === 'json') {
content = JSON.stringify(aContent, undefined, ' '); content = JSON.stringify(content, undefined, ' ');
} }
const file = new Blob([<string>content], { const file = new Blob([<string>content], {
type: aContentType type: contentType
}); });
a.href = URL.createObjectURL(file); a.href = URL.createObjectURL(file);
a.download = aFileName; a.download = fileName;
a.click(); a.click();
} }

Loading…
Cancel
Save