Feature/set up event system for portfolio changes (#3333)
* Set up event system for portfolio changes * Update changelog
This commit is contained in:
parent
a4efbc0131
commit
b692b7432c
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Extended the content of the _Self-Hosting_ section by the custom asset instructions on the Frequently Asked Questions (FAQ) page
|
- Extended the content of the _Self-Hosting_ section by the custom asset instructions on the Frequently Asked Questions (FAQ) page
|
||||||
|
- Set up an event system to follow portfolio changes
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator';
|
||||||
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard';
|
||||||
import { resetHours } from '@ghostfolio/common/helper';
|
|
||||||
import { permissions } from '@ghostfolio/common/permissions';
|
import { permissions } from '@ghostfolio/common/permissions';
|
||||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
@ -18,7 +17,6 @@ import {
|
|||||||
import { REQUEST } from '@nestjs/core';
|
import { REQUEST } from '@nestjs/core';
|
||||||
import { AuthGuard } from '@nestjs/passport';
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
import { AccountBalance } from '@prisma/client';
|
import { AccountBalance } from '@prisma/client';
|
||||||
import { parseISO } from 'date-fns';
|
|
||||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||||
|
|
||||||
import { AccountBalanceService } from './account-balance.service';
|
import { AccountBalanceService } from './account-balance.service';
|
||||||
@ -67,10 +65,11 @@ export class AccountBalanceController {
|
|||||||
@Param('id') id: string
|
@Param('id') id: string
|
||||||
): Promise<AccountBalance> {
|
): Promise<AccountBalance> {
|
||||||
const accountBalance = await this.accountBalanceService.accountBalance({
|
const accountBalance = await this.accountBalanceService.accountBalance({
|
||||||
id
|
id,
|
||||||
|
userId: this.request.user.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!accountBalance || accountBalance.userId !== this.request.user.id) {
|
if (!accountBalance) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
StatusCodes.FORBIDDEN
|
StatusCodes.FORBIDDEN
|
||||||
@ -78,7 +77,8 @@ export class AccountBalanceController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return this.accountBalanceService.deleteAccountBalance({
|
return this.accountBalanceService.deleteAccountBalance({
|
||||||
id
|
id: accountBalance.id,
|
||||||
|
userId: accountBalance.userId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
import { resetHours } from '@ghostfolio/common/helper';
|
import { resetHours } from '@ghostfolio/common/helper';
|
||||||
@ -5,6 +6,7 @@ import { AccountBalancesResponse, Filter } from '@ghostfolio/common/interfaces';
|
|||||||
import { UserWithSettings } from '@ghostfolio/common/types';
|
import { UserWithSettings } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { AccountBalance, Prisma } from '@prisma/client';
|
import { AccountBalance, Prisma } from '@prisma/client';
|
||||||
import { parseISO } from 'date-fns';
|
import { parseISO } from 'date-fns';
|
||||||
|
|
||||||
@ -13,6 +15,7 @@ import { CreateAccountBalanceDto } from './create-account-balance.dto';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class AccountBalanceService {
|
export class AccountBalanceService {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly prismaService: PrismaService
|
private readonly prismaService: PrismaService
|
||||||
) {}
|
) {}
|
||||||
@ -36,7 +39,7 @@ export class AccountBalanceService {
|
|||||||
}: CreateAccountBalanceDto & {
|
}: CreateAccountBalanceDto & {
|
||||||
userId: string;
|
userId: string;
|
||||||
}): Promise<AccountBalance> {
|
}): Promise<AccountBalance> {
|
||||||
return this.prismaService.accountBalance.upsert({
|
const accountBalance = await this.prismaService.accountBalance.upsert({
|
||||||
create: {
|
create: {
|
||||||
Account: {
|
Account: {
|
||||||
connect: {
|
connect: {
|
||||||
@ -59,14 +62,32 @@ export class AccountBalanceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return accountBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteAccountBalance(
|
public async deleteAccountBalance(
|
||||||
where: Prisma.AccountBalanceWhereUniqueInput
|
where: Prisma.AccountBalanceWhereUniqueInput
|
||||||
): Promise<AccountBalance> {
|
): Promise<AccountBalance> {
|
||||||
return this.prismaService.accountBalance.delete({
|
const accountBalance = await this.prismaService.accountBalance.delete({
|
||||||
where
|
where
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: <string>where.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return accountBalance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAccountBalances({
|
public async getAccountBalances({
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
|
import { AccountBalanceService } from '@ghostfolio/api/app/account-balance/account-balance.service';
|
||||||
|
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||||
import { Filter } from '@ghostfolio/common/interfaces';
|
import { Filter } from '@ghostfolio/common/interfaces';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { Account, Order, Platform, Prisma } from '@prisma/client';
|
import { Account, Order, Platform, Prisma } from '@prisma/client';
|
||||||
import { Big } from 'big.js';
|
import { Big } from 'big.js';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
@ -16,6 +18,7 @@ import { CashDetails } from './interfaces/cash-details.interface';
|
|||||||
export class AccountService {
|
export class AccountService {
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly accountBalanceService: AccountBalanceService,
|
private readonly accountBalanceService: AccountBalanceService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly prismaService: PrismaService
|
private readonly prismaService: PrismaService
|
||||||
) {}
|
) {}
|
||||||
@ -94,6 +97,13 @@ export class AccountService {
|
|||||||
userId: aUserId
|
userId: aUserId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: account.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,9 +111,18 @@ export class AccountService {
|
|||||||
where: Prisma.AccountWhereUniqueInput,
|
where: Prisma.AccountWhereUniqueInput,
|
||||||
aUserId: string
|
aUserId: string
|
||||||
): Promise<Account> {
|
): Promise<Account> {
|
||||||
return this.prismaService.account.delete({
|
const account = await this.prismaService.account.delete({
|
||||||
where
|
where
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: account.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAccounts(aUserId: string): Promise<Account[]> {
|
public async getAccounts(aUserId: string): Promise<Account[]> {
|
||||||
@ -201,10 +220,19 @@ export class AccountService {
|
|||||||
userId: aUserId
|
userId: aUserId
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.prismaService.account.update({
|
const account = await this.prismaService.account.update({
|
||||||
data,
|
data,
|
||||||
where
|
where
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: account.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateAccountBalance({
|
public async updateAccountBalance({
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { EventsModule } from '@ghostfolio/api/events/events.module';
|
||||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||||
import { CronService } from '@ghostfolio/api/services/cron.service';
|
import { CronService } from '@ghostfolio/api/services/cron.service';
|
||||||
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
|
import { DataGatheringModule } from '@ghostfolio/api/services/data-gathering/data-gathering.module';
|
||||||
@ -14,6 +15,7 @@ import {
|
|||||||
import { BullModule } from '@nestjs/bull';
|
import { BullModule } from '@nestjs/bull';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
import { ScheduleModule } from '@nestjs/schedule';
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { ServeStaticModule } from '@nestjs/serve-static';
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
import { StatusCodes } from 'http-status-codes';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
@ -44,6 +46,7 @@ import { TagModule } from './tag/tag.module';
|
|||||||
import { UserModule } from './user/user.module';
|
import { UserModule } from './user/user.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
controllers: [AppController],
|
||||||
imports: [
|
imports: [
|
||||||
AdminModule,
|
AdminModule,
|
||||||
AccessModule,
|
AccessModule,
|
||||||
@ -64,6 +67,8 @@ import { UserModule } from './user/user.module';
|
|||||||
ConfigurationModule,
|
ConfigurationModule,
|
||||||
DataGatheringModule,
|
DataGatheringModule,
|
||||||
DataProviderModule,
|
DataProviderModule,
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
|
EventsModule,
|
||||||
ExchangeRateModule,
|
ExchangeRateModule,
|
||||||
ExchangeRateDataModule,
|
ExchangeRateDataModule,
|
||||||
ExportModule,
|
ExportModule,
|
||||||
@ -109,7 +114,6 @@ import { UserModule } from './user/user.module';
|
|||||||
TwitterBotModule,
|
TwitterBotModule,
|
||||||
UserModule
|
UserModule
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
|
||||||
providers: [CronService]
|
providers: [CronService]
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
@ -60,15 +60,15 @@ export class OrderController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
|
@HasPermission(permissions.deleteOrder)
|
||||||
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
|
||||||
public async deleteOrder(@Param('id') id: string): Promise<OrderModel> {
|
public async deleteOrder(@Param('id') id: string): Promise<OrderModel> {
|
||||||
const order = await this.orderService.order({ id });
|
const order = await this.orderService.order({
|
||||||
|
id,
|
||||||
|
userId: this.request.user.id
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (!order) {
|
||||||
!hasPermission(this.request.user.permissions, permissions.deleteOrder) ||
|
|
||||||
!order ||
|
|
||||||
order.userId !== this.request.user.id
|
|
||||||
) {
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||||
StatusCodes.FORBIDDEN
|
StatusCodes.FORBIDDEN
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
import { AccountService } from '@ghostfolio/api/app/account/account.service';
|
||||||
|
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
|
||||||
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
|
import { DataGatheringService } from '@ghostfolio/api/services/data-gathering/data-gathering.service';
|
||||||
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data/exchange-rate-data.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
@ -13,6 +14,7 @@ import { Filter, UniqueAsset } from '@ghostfolio/common/interfaces';
|
|||||||
import { OrderWithAccount } from '@ghostfolio/common/types';
|
import { OrderWithAccount } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import {
|
import {
|
||||||
AssetClass,
|
AssetClass,
|
||||||
AssetSubClass,
|
AssetSubClass,
|
||||||
@ -27,7 +29,6 @@ import { endOfToday, isAfter } from 'date-fns';
|
|||||||
import { groupBy, uniqBy } from 'lodash';
|
import { groupBy, uniqBy } from 'lodash';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { CreateOrderDto } from './create-order.dto';
|
|
||||||
import { Activities } from './interfaces/activities.interface';
|
import { Activities } from './interfaces/activities.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -35,6 +36,7 @@ export class OrderService {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly accountService: AccountService,
|
private readonly accountService: AccountService,
|
||||||
private readonly dataGatheringService: DataGatheringService,
|
private readonly dataGatheringService: DataGatheringService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
private readonly exchangeRateDataService: ExchangeRateDataService,
|
private readonly exchangeRateDataService: ExchangeRateDataService,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
private readonly symbolProfileService: SymbolProfileService
|
private readonly symbolProfileService: SymbolProfileService
|
||||||
@ -160,6 +162,13 @@ export class OrderService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: order.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +183,13 @@ export class OrderService {
|
|||||||
await this.symbolProfileService.deleteById(order.symbolProfileId);
|
await this.symbolProfileService.deleteById(order.symbolProfileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: order.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,6 +198,13 @@ export class OrderService {
|
|||||||
where
|
where
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: <string>where.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,7 +478,7 @@ export class OrderService {
|
|||||||
where
|
where
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.prismaService.order.update({
|
const order = await this.prismaService.order.update({
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
isDraft,
|
isDraft,
|
||||||
@ -467,6 +490,15 @@ export class OrderService {
|
|||||||
},
|
},
|
||||||
where
|
where
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId: order.userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async orders(params: {
|
private async orders(params: {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
|
||||||
import { environment } from '@ghostfolio/api/environments/environment';
|
import { environment } from '@ghostfolio/api/environments/environment';
|
||||||
|
import { PortfolioChangedEvent } from '@ghostfolio/api/events/portfolio-changed.event';
|
||||||
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
|
||||||
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
|
import { I18nService } from '@ghostfolio/api/services/i18n/i18n.service';
|
||||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||||
@ -25,6 +26,7 @@ import {
|
|||||||
import { UserWithSettings } from '@ghostfolio/common/types';
|
import { UserWithSettings } from '@ghostfolio/common/types';
|
||||||
|
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { Prisma, Role, User } from '@prisma/client';
|
import { Prisma, Role, User } from '@prisma/client';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
import { sortBy, without } from 'lodash';
|
import { sortBy, without } from 'lodash';
|
||||||
@ -37,6 +39,7 @@ export class UserService {
|
|||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly configurationService: ConfigurationService,
|
private readonly configurationService: ConfigurationService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
private readonly prismaService: PrismaService,
|
private readonly prismaService: PrismaService,
|
||||||
private readonly propertyService: PropertyService,
|
private readonly propertyService: PropertyService,
|
||||||
private readonly subscriptionService: SubscriptionService,
|
private readonly subscriptionService: SubscriptionService,
|
||||||
@ -437,11 +440,9 @@ export class UserService {
|
|||||||
userId: string;
|
userId: string;
|
||||||
userSettings: UserSettings;
|
userSettings: UserSettings;
|
||||||
}) {
|
}) {
|
||||||
const settings = userSettings as unknown as Prisma.JsonObject;
|
const { settings } = await this.prismaService.settings.upsert({
|
||||||
|
|
||||||
await this.prismaService.settings.upsert({
|
|
||||||
create: {
|
create: {
|
||||||
settings,
|
settings: userSettings as unknown as Prisma.JsonObject,
|
||||||
User: {
|
User: {
|
||||||
connect: {
|
connect: {
|
||||||
id: userId
|
id: userId
|
||||||
@ -449,14 +450,21 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
settings
|
settings: userSettings as unknown as Prisma.JsonObject
|
||||||
},
|
},
|
||||||
where: {
|
where: {
|
||||||
userId
|
userId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
this.eventEmitter.emit(
|
||||||
|
PortfolioChangedEvent.getName(),
|
||||||
|
new PortfolioChangedEvent({
|
||||||
|
userId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRandomString(length: number) {
|
private getRandomString(length: number) {
|
||||||
|
8
apps/api/src/events/events.module.ts
Normal file
8
apps/api/src/events/events.module.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PortfolioChangedListener } from './portfolio-changed.listener';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [PortfolioChangedListener]
|
||||||
|
})
|
||||||
|
export class EventsModule {}
|
15
apps/api/src/events/portfolio-changed.event.ts
Normal file
15
apps/api/src/events/portfolio-changed.event.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export class PortfolioChangedEvent {
|
||||||
|
private userId: string;
|
||||||
|
|
||||||
|
public constructor({ userId }: { userId: string }) {
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getName() {
|
||||||
|
return 'portfolio.changed';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUserId() {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
}
|
15
apps/api/src/events/portfolio-changed.listener.ts
Normal file
15
apps/api/src/events/portfolio-changed.listener.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { OnEvent } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
import { PortfolioChangedEvent } from './portfolio-changed.event';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PortfolioChangedListener {
|
||||||
|
@OnEvent(PortfolioChangedEvent.getName())
|
||||||
|
handlePortfolioChangedEvent(event: PortfolioChangedEvent) {
|
||||||
|
Logger.log(
|
||||||
|
`Portfolio of user with id ${event.getUserId()} has changed`,
|
||||||
|
'PortfolioChangedListener'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -78,6 +78,7 @@
|
|||||||
"@nestjs/common": "10.1.3",
|
"@nestjs/common": "10.1.3",
|
||||||
"@nestjs/config": "3.0.0",
|
"@nestjs/config": "3.0.0",
|
||||||
"@nestjs/core": "10.1.3",
|
"@nestjs/core": "10.1.3",
|
||||||
|
"@nestjs/event-emitter": "2.0.4",
|
||||||
"@nestjs/jwt": "10.1.0",
|
"@nestjs/jwt": "10.1.0",
|
||||||
"@nestjs/passport": "10.0.0",
|
"@nestjs/passport": "10.0.0",
|
||||||
"@nestjs/platform-express": "10.1.3",
|
"@nestjs/platform-express": "10.1.3",
|
||||||
|
@ -4814,6 +4814,13 @@
|
|||||||
path-to-regexp "3.2.0"
|
path-to-regexp "3.2.0"
|
||||||
tslib "2.6.1"
|
tslib "2.6.1"
|
||||||
|
|
||||||
|
"@nestjs/event-emitter@2.0.4":
|
||||||
|
version "2.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz#de7d3986cfeb82639bb95181fab4fe5525437c74"
|
||||||
|
integrity sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==
|
||||||
|
dependencies:
|
||||||
|
eventemitter2 "6.4.9"
|
||||||
|
|
||||||
"@nestjs/jwt@10.1.0":
|
"@nestjs/jwt@10.1.0":
|
||||||
version "10.1.0"
|
version "10.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-10.1.0.tgz#7899b68d68b998cc140bc0af392c63fc00755236"
|
resolved "https://registry.yarnpkg.com/@nestjs/jwt/-/jwt-10.1.0.tgz#7899b68d68b998cc140bc0af392c63fc00755236"
|
||||||
@ -11653,7 +11660,7 @@ event-target-shim@^5.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789"
|
||||||
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==
|
||||||
|
|
||||||
eventemitter2@^6.4.2:
|
eventemitter2@6.4.9, eventemitter2@^6.4.2:
|
||||||
version "6.4.9"
|
version "6.4.9"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125"
|
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125"
|
||||||
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
|
integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user