Browse Source

Add account page logic

pull/58/head
Thomas 4 years ago
parent
commit
0eeb5591b6
  1. 91
      apps/api/src/app/account/account.controller.ts
  2. 4
      apps/api/src/app/account/account.module.ts
  3. 82
      apps/api/src/app/account/account.service.ts
  4. 2
      apps/client/src/app/app.component.ts
  5. 51
      apps/client/src/app/components/accounts-table/accounts-table.component.html
  6. 18
      apps/client/src/app/components/accounts-table/accounts-table.component.ts
  7. 2
      apps/client/src/app/components/transactions-table/transactions-table.component.html
  8. 30
      apps/client/src/app/pages/accounts/accounts-page.component.ts
  9. 8
      apps/client/src/app/pages/accounts/accounts-page.html
  10. 4
      apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html
  11. 5
      libs/helper/src/lib/permissions.ts

91
apps/api/src/app/account/account.controller.ts

@ -17,8 +17,7 @@ import {
} from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Order as OrderModel } from '@prisma/client';
import { parseISO } from 'date-fns';
import { Account as AccountModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { AccountService } from './account.service';
@ -35,11 +34,11 @@ export class AccountController {
@Delete(':id')
@UseGuards(AuthGuard('jwt'))
public async deleteOrder(@Param('id') id: string): Promise<OrderModel> {
public async deleteAccount(@Param('id') id: string): Promise<AccountModel> {
if (
!hasPermission(
getPermissions(this.request.user.role),
permissions.deleteOrder
permissions.deleteAccount
)
) {
throw new HttpException(
@ -48,7 +47,7 @@ export class AccountController {
);
}
return this.accountService.deleteOrder(
return this.accountService.deleteAccount(
{
id_userId: {
id,
@ -61,23 +60,17 @@ export class AccountController {
@Get()
@UseGuards(AuthGuard('jwt'))
public async getAllOrders(
public async getAllAccounts(
@Headers('impersonation-id') impersonationId
): Promise<OrderModel[]> {
): Promise<AccountModel[]> {
const impersonationUserId = await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
let orders = await this.accountService.orders({
include: {
Account: {
include: {
Platform: true
}
}
},
orderBy: { date: 'desc' },
let accounts = await this.accountService.accounts({
include: { Platform: true },
orderBy: { name: 'desc' },
where: { userId: impersonationUserId || this.request.user.id }
});
@ -88,16 +81,20 @@ export class AccountController {
permissions.readForeignPortfolio
)
) {
orders = nullifyValuesInObjects(orders, ['fee', 'quantity', 'unitPrice']);
accounts = nullifyValuesInObjects(accounts, [
'fee',
'quantity',
'unitPrice'
]);
}
return orders;
return accounts;
}
@Get(':id')
@UseGuards(AuthGuard('jwt'))
public async getOrderById(@Param('id') id: string): Promise<OrderModel> {
return this.accountService.order({
public async getAccountById(@Param('id') id: string): Promise<AccountModel> {
return this.accountService.account({
id_userId: {
id,
userId: this.request.user.id
@ -107,13 +104,13 @@ export class AccountController {
@Post()
@UseGuards(AuthGuard('jwt'))
public async createOrder(
public async createAccount(
@Body() data: CreateAccountDto
): Promise<OrderModel> {
): Promise<AccountModel> {
if (
!hasPermission(
getPermissions(this.request.user.role),
permissions.createOrder
permissions.createAccount
)
) {
throw new HttpException(
@ -122,24 +119,13 @@ export class AccountController {
);
}
const date = parseISO(data.date);
const accountId = data.accountId;
delete data.accountId;
if (data.platformId) {
const platformId = data.platformId;
delete data.platformId;
return this.accountService.createOrder(
return this.accountService.createAccount(
{
...data,
date,
Account: {
connect: {
id_userId: { id: accountId, userId: this.request.user.id }
}
},
Platform: { connect: { id: platformId } },
User: { connect: { id: this.request.user.id } }
},
@ -148,15 +134,9 @@ export class AccountController {
} else {
delete data.platformId;
return this.accountService.createOrder(
return this.accountService.createAccount(
{
...data,
date,
Account: {
connect: {
id_userId: { id: accountId, userId: this.request.user.id }
}
},
User: { connect: { id: this.request.user.id } }
},
this.request.user.id
@ -170,7 +150,7 @@ export class AccountController {
if (
!hasPermission(
getPermissions(this.request.user.role),
permissions.updateOrder
permissions.updateAccount
)
) {
throw new HttpException(
@ -179,32 +159,21 @@ export class AccountController {
);
}
const originalOrder = await this.accountService.order({
const originalAccount = await this.accountService.account({
id_userId: {
id,
userId: this.request.user.id
}
});
const date = parseISO(data.date);
const accountId = data.accountId;
delete data.accountId;
if (data.platformId) {
const platformId = data.platformId;
delete data.platformId;
return this.accountService.updateOrder(
return this.accountService.updateAccount(
{
data: {
...data,
date,
Account: {
connect: {
id_userId: { id: accountId, userId: this.request.user.id }
}
},
Platform: { connect: { id: platformId } },
User: { connect: { id: this.request.user.id } }
},
@ -221,17 +190,11 @@ export class AccountController {
// platformId is null, remove it
delete data.platformId;
return this.accountService.updateOrder(
return this.accountService.updateAccount(
{
data: {
...data,
date,
Account: {
connect: {
id_userId: { id: accountId, userId: this.request.user.id }
}
},
Platform: originalOrder.platformId
Platform: originalAccount.platformId
? { disconnect: true }
: undefined,
User: { connect: { id: this.request.user.id } }

4
apps/api/src/app/account/account.module.ts

@ -1,5 +1,4 @@
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';
@ -9,7 +8,6 @@ import { ImpersonationService } from '@ghostfolio/api/services/impersonation.ser
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Module } from '@nestjs/common';
import { CacheService } from '../cache/cache.service';
import { RedisCacheModule } from '../redis-cache/redis-cache.module';
import { AccountController } from './account.controller';
import { AccountService } from './account.service';
@ -20,9 +18,7 @@ import { AccountService } from './account.service';
providers: [
AccountService,
AlphaVantageService,
CacheService,
ConfigurationService,
DataGatheringService,
DataProviderService,
GhostfolioScraperApiService,
ImpersonationService,

82
apps/api/src/app/account/account.service.ts

@ -1,40 +1,35 @@
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering.service';
import { PrismaService } from '@ghostfolio/api/services/prisma.service';
import { Injectable } from '@nestjs/common';
import { Order, Prisma } from '@prisma/client';
import { Account, Prisma } from '@prisma/client';
import { CacheService } from '../cache/cache.service';
import { RedisCacheService } from '../redis-cache/redis-cache.service';
import { OrderWithPlatform } from './interfaces/order-with-platform.type';
@Injectable()
export class AccountService {
public constructor(
private readonly cacheService: CacheService,
private readonly dataGatheringService: DataGatheringService,
private readonly redisCacheService: RedisCacheService,
private prisma: PrismaService
) {}
public async order(
orderWhereUniqueInput: Prisma.OrderWhereUniqueInput
): Promise<Order | null> {
return this.prisma.order.findUnique({
where: orderWhereUniqueInput
public async account(
accountWhereUniqueInput: Prisma.AccountWhereUniqueInput
): Promise<Account | null> {
return this.prisma.account.findUnique({
where: accountWhereUniqueInput
});
}
public async orders(params: {
include?: Prisma.OrderInclude;
public async accounts(params: {
include?: Prisma.AccountInclude;
skip?: number;
take?: number;
cursor?: Prisma.OrderWhereUniqueInput;
where?: Prisma.OrderWhereInput;
orderBy?: Prisma.OrderOrderByInput;
}): Promise<OrderWithPlatform[]> {
cursor?: Prisma.AccountWhereUniqueInput;
where?: Prisma.AccountWhereInput;
orderBy?: Prisma.AccountOrderByInput;
}): Promise<Account[]> {
const { include, skip, take, cursor, where, orderBy } = params;
return this.prisma.order.findMany({
return this.prisma.account.findMany({
cursor,
include,
orderBy,
@ -44,60 +39,35 @@ export class AccountService {
});
}
public async createOrder(
data: Prisma.OrderCreateInput,
public async createAccount(
data: Prisma.AccountCreateInput,
aUserId: string
): Promise<Order> {
this.redisCacheService.remove(`${aUserId}.portfolio`);
// Gather symbol data of order in the background
this.dataGatheringService.gatherSymbols([
{
date: <Date>data.date,
symbol: data.symbol
}
]);
await this.cacheService.flush(aUserId);
return this.prisma.order.create({
): Promise<Account> {
return this.prisma.account.create({
data
});
}
public async deleteOrder(
where: Prisma.OrderWhereUniqueInput,
public async deleteAccount(
where: Prisma.AccountWhereUniqueInput,
aUserId: string
): Promise<Order> {
): Promise<Account> {
this.redisCacheService.remove(`${aUserId}.portfolio`);
return this.prisma.order.delete({
return this.prisma.account.delete({
where
});
}
public async updateOrder(
public async updateAccount(
params: {
where: Prisma.OrderWhereUniqueInput;
data: Prisma.OrderUpdateInput;
where: Prisma.AccountWhereUniqueInput;
data: Prisma.AccountUpdateInput;
},
aUserId: string
): Promise<Order> {
): Promise<Account> {
const { data, where } = params;
this.redisCacheService.remove(`${aUserId}.portfolio`);
// Gather symbol data of order in the background
this.dataGatheringService.gatherSymbols([
{
date: <Date>data.date,
symbol: <string>data.symbol
}
]);
await this.cacheService.flush(aUserId);
return this.prisma.order.update({
return this.prisma.account.update({
data,
where
});

2
apps/client/src/app/app.component.ts

@ -74,7 +74,7 @@ export class AppComponent implements OnDestroy, OnInit {
this.canCreateAccount = hasPermission(
this.user.permissions,
permissions.createAccount
permissions.createUserAccount
);
this.cd.markForCheck();

51
apps/client/src/app/components/accounts-table/accounts-table.component.html

@ -9,49 +9,44 @@
<ng-container matColumnDef="account">
<th *matHeaderCellDef i18n mat-header-cell mat-sort-header>Name</th>
<td *matCellDef="let element" mat-cell>
<div class="d-flex">
<gf-symbol-icon
*ngIf="element.Account?.Platform?.url"
class="mr-2"
[tooltip]="element.Account?.Platform?.name"
[url]="element.Account?.Platform?.url"
></gf-symbol-icon>
<span class="d-none d-lg-block">{{ element.Account?.name }}</span>
</div>
{{ element.name }}
</td>
</ng-container>
<ng-container matColumnDef="type">
<th
*matHeaderCellDef
class="justify-content-center"
class="d-none d-lg-table-cell justify-content-center"
i18n
mat-header-cell
mat-sort-header
>
Type
</th>
<td mat-cell *matCellDef="let element" class="text-center">
<div
class="d-inline-flex justify-content-center pl-1 pr-2 py-1 type-badge"
[ngClass]="element.type == 'BUY' ? 'buy' : 'sell'"
>
<ion-icon
class="mr-1"
[name]="
element.type === 'BUY'
? 'arrow-forward-circle-outline'
: 'arrow-back-circle-outline'
"
></ion-icon>
<span>{{ element.type }}</span>
<td
mat-cell
*matCellDef="let element"
class="d-none d-lg-table-cell text-center"
>
<div class="d-inline-flex justify-content-center px-2 py-1 type-badge">
<span>{{ element.accountType }}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="symbol">
<ng-container matColumnDef="platform">
<th *matHeaderCellDef i18n mat-header-cell mat-sort-header>Platform</th>
<td mat-cell *matCellDef="let element">{{ element.symbol }}</td>
<td mat-cell *matCellDef="let element">
<div class="d-flex">
<gf-symbol-icon
*ngIf="element.Platform?.url"
class="mr-1"
[tooltip]=""
[url]="element.Platform?.url"
></gf-symbol-icon>
<span>{{ element.Platform?.name }}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="actions">
@ -66,10 +61,10 @@
<ion-icon name="ellipsis-vertical"></ion-icon>
</button>
<mat-menu #accountMenu="matMenu" xPosition="before">
<button i18n mat-menu-item (click)="onUpdateTransaction(element)">
<button i18n mat-menu-item (click)="onUpdateAccount(element)">
Edit
</button>
<button i18n mat-menu-item (click)="onDeleteTransaction(element.id)">
<button i18n mat-menu-item (click)="onDeleteAccount(element.id)">
Delete
</button>
</mat-menu>

18
apps/client/src/app/components/accounts-table/accounts-table.component.ts

@ -29,8 +29,8 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
@Input() locale: string;
@Input() showActions: boolean;
@Output() transactionDeleted = new EventEmitter<string>();
@Output() transactionToUpdate = new EventEmitter<OrderModel>();
@Output() accountDeleted = new EventEmitter<string>();
@Output() accountToUpdate = new EventEmitter<OrderModel>();
@ViewChild(MatSort) sort: MatSort;
@ -50,7 +50,7 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
public ngOnInit() {}
public ngOnChanges() {
this.displayedColumns = ['account', 'type', 'symbol'];
this.displayedColumns = ['account', 'type', 'platform'];
this.isLoading = true;
@ -66,18 +66,16 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
}
}
public onDeleteTransaction(aId: string) {
const confirmation = confirm(
'Do you really want to delete this transaction?'
);
public onDeleteAccount(aId: string) {
const confirmation = confirm('Do you really want to delete this account?');
if (confirmation) {
this.transactionDeleted.emit(aId);
this.accountDeleted.emit(aId);
}
}
public onUpdateTransaction(aTransaction: OrderModel) {
this.transactionToUpdate.emit(aTransaction);
public onUpdateAccount(aAccount: OrderModel) {
this.accountToUpdate.emit(aAccount);
}
public ngOnDestroy() {

2
apps/client/src/app/components/transactions-table/transactions-table.component.html

@ -23,7 +23,7 @@
<div class="d-flex">
<gf-symbol-icon
*ngIf="element.Account?.Platform?.url"
class="mr-2"
class="mr-1"
[tooltip]="element.Account?.Platform?.name"
[url]="element.Account?.Platform?.url"
></gf-symbol-icon>

30
apps/client/src/app/pages/accounts/accounts-page.component.ts

@ -23,8 +23,8 @@ export class AccountsPageComponent implements OnInit {
public accounts: OrderModel[];
public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean;
public hasPermissionToDeleteOrder: boolean;
public hasPermissionToCreateAccount: boolean;
public hasPermissionToDeleteAccount: boolean;
public routeQueryParams: Subscription;
public user: User;
@ -47,14 +47,14 @@ export class AccountsPageComponent implements OnInit {
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((params) => {
if (params['createDialog']) {
this.openCreateTransactionDialog();
this.openCreateAccountDialog();
} else if (params['editDialog']) {
if (this.accounts) {
const transaction = this.accounts.find((transaction) => {
return transaction.id === params['transactionId'];
const account = this.accounts.find((account) => {
return account.id === params['transactionId'];
});
this.openUpdateTransactionDialog(transaction);
this.openUpdateAccountDialog(account);
} else {
this.router.navigate(['.'], { relativeTo: this.route });
}
@ -80,13 +80,13 @@ export class AccountsPageComponent implements OnInit {
.subscribe(() => {
this.dataService.fetchUser().subscribe((user) => {
this.user = user;
this.hasPermissionToCreateOrder = hasPermission(
this.hasPermissionToCreateAccount = hasPermission(
user.permissions,
permissions.createOrder
permissions.createAccount
);
this.hasPermissionToDeleteOrder = hasPermission(
this.hasPermissionToDeleteAccount = hasPermission(
user.permissions,
permissions.deleteOrder
permissions.deleteAccount
);
this.cd.markForCheck();
@ -108,7 +108,7 @@ export class AccountsPageComponent implements OnInit {
});
}
public onDeleteTransaction(aId: string) {
public onDeleteAccount(aId: string) {
this.dataService.deleteAccount(aId).subscribe({
next: () => {
this.fetchAccounts();
@ -116,13 +116,13 @@ export class AccountsPageComponent implements OnInit {
});
}
public onUpdateTransaction(aTransaction: OrderModel) {
public onUpdateAccount(aAccount: OrderModel) {
this.router.navigate([], {
queryParams: { editDialog: true, transactionId: aTransaction.id }
queryParams: { editDialog: true, transactionId: aAccount.id }
});
}
public openUpdateTransactionDialog({
public openUpdateAccountDialog({
accountId,
currency,
dataSource,
@ -176,7 +176,7 @@ export class AccountsPageComponent implements OnInit {
this.unsubscribeSubject.complete();
}
private openCreateTransactionDialog(): void {
private openCreateAccountDialog(): void {
const dialogRef = this.dialog.open(CreateOrUpdateAccountDialog, {
data: {
accounts: this.user?.accounts,

8
apps/client/src/app/pages/accounts/accounts-page.html

@ -7,15 +7,15 @@
[baseCurrency]="user?.settings?.baseCurrency"
[deviceType]="deviceType"
[locale]="user?.settings?.locale"
[showActions]="!hasImpersonationId && hasPermissionToDeleteOrder"
(transactionDeleted)="onDeleteTransaction($event)"
(transactionToUpdate)="onUpdateTransaction($event)"
[showActions]="!hasImpersonationId && hasPermissionToDeleteAccount"
(accountDeleted)="onDeleteAccount($event)"
(accountToUpdate)="onUpdateAccount($event)"
></gf-accounts-table>
</div>
</div>
<div
*ngIf="!hasImpersonationId && hasPermissionToCreateOrder"
*ngIf="!hasImpersonationId && hasPermissionToCreateAccount"
class="fab-container"
>
<a

4
apps/client/src/app/pages/accounts/create-or-update-account-dialog/create-or-update-account-dialog.html

@ -1,4 +1,4 @@
<form #addTransactionForm="ngForm" class="d-flex flex-column h-100">
<form #addAccountForm="ngForm" class="d-flex flex-column h-100">
<h1 *ngIf="data.transaction.id" mat-dialog-title i18n>Update account</h1>
<h1 *ngIf="!data.transaction.id" mat-dialog-title i18n>Add account</h1>
<div class="flex-grow-1" mat-dialog-content>
@ -154,7 +154,7 @@
color="primary"
i18n
mat-flat-button
[disabled]="!(addTransactionForm.form.valid && data.transaction.symbol)"
[disabled]="!(addAccountForm.form.valid && data.transaction.symbol)"
[mat-dialog-close]="data"
>
Save

5
libs/helper/src/lib/permissions.ts

@ -9,10 +9,13 @@ export const permissions = {
accessFearAndGreedIndex: 'accessFearAndGreedIndex',
createAccount: 'createAccount',
createOrder: 'createOrder',
createUserAccount: 'createUserAccount',
deleteAccount: 'deleteAcccount',
deleteOrder: 'deleteOrder',
enableSocialLogin: 'enableSocialLogin',
enableSubscription: 'enableSubscription',
readForeignPortfolio: 'readForeignPortfolio',
updateAccount: 'updateAccount',
updateOrder: 'updateOrder',
updateUserSettings: 'updateUserSettings'
};
@ -37,7 +40,7 @@ export function getPermissions(aRole: Role): string[] {
];
case 'DEMO':
return [permissions.createAccount];
return [permissions.createUserAccount];
case 'USER':
return [

Loading…
Cancel
Save