mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
7 changed files with 213 additions and 2 deletions
@ -0,0 +1,10 @@ |
|||||
|
import { DataSource } from '@prisma/client'; |
||||
|
import { IsEnum, IsString } from 'class-validator'; |
||||
|
|
||||
|
export class CreateWatchlistItemDto { |
||||
|
@IsEnum(DataSource) |
||||
|
dataSource: DataSource; |
||||
|
|
||||
|
@IsString() |
||||
|
symbol: string; |
||||
|
} |
@ -0,0 +1,85 @@ |
|||||
|
import { HasPermission } from '@ghostfolio/api/decorators/has-permission.decorator'; |
||||
|
import { HasPermissionGuard } from '@ghostfolio/api/guards/has-permission.guard'; |
||||
|
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.interceptor'; |
||||
|
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.interceptor'; |
||||
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; |
||||
|
import { permissions } from '@ghostfolio/common/permissions'; |
||||
|
import { RequestWithUser } from '@ghostfolio/common/types'; |
||||
|
|
||||
|
import { |
||||
|
Body, |
||||
|
Controller, |
||||
|
Delete, |
||||
|
Get, |
||||
|
HttpException, |
||||
|
Inject, |
||||
|
Param, |
||||
|
Post, |
||||
|
UseGuards, |
||||
|
UseInterceptors |
||||
|
} from '@nestjs/common'; |
||||
|
import { REQUEST } from '@nestjs/core'; |
||||
|
import { AuthGuard } from '@nestjs/passport'; |
||||
|
import { DataSource } from '@prisma/client'; |
||||
|
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; |
||||
|
|
||||
|
import { CreateWatchlistItemDto } from './create-watchlist-item.dto'; |
||||
|
import { WatchlistService } from './watchlist.service'; |
||||
|
|
||||
|
@Controller('watchlist') |
||||
|
export class WatchlistController { |
||||
|
public constructor( |
||||
|
@Inject(REQUEST) private readonly request: RequestWithUser, |
||||
|
private readonly watchlistService: WatchlistService |
||||
|
) {} |
||||
|
|
||||
|
@Post() |
||||
|
@HasPermission(permissions.createWatchlistItem) |
||||
|
@UseGuards(AuthGuard('jwt')) |
||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
||||
|
public async createWatchlistItem(@Body() data: CreateWatchlistItemDto) { |
||||
|
return this.watchlistService.createWatchlistItem({ |
||||
|
dataSource: data.dataSource, |
||||
|
symbol: data.symbol, |
||||
|
userId: this.request.user.id |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Delete(':dataSource/:symbol') |
||||
|
@HasPermission(permissions.deleteWatchlistItem) |
||||
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
||||
|
@UseInterceptors(TransformDataSourceInRequestInterceptor) |
||||
|
public async deleteWatchlistItem( |
||||
|
@Param('dataSource') dataSource: DataSource, |
||||
|
@Param('symbol') symbol: string |
||||
|
) { |
||||
|
const watchlistItem = await this.watchlistService |
||||
|
.getWatchlistItems(this.request.user.id) |
||||
|
.then((items) => { |
||||
|
return items.find((item) => { |
||||
|
return item.dataSource === dataSource && item.symbol === symbol; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
if (!watchlistItem) { |
||||
|
throw new HttpException( |
||||
|
getReasonPhrase(StatusCodes.NOT_FOUND), |
||||
|
StatusCodes.NOT_FOUND |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
return this.watchlistService.deleteWatchlistItem({ |
||||
|
dataSource, |
||||
|
symbol, |
||||
|
userId: this.request.user.id |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Get() |
||||
|
@HasPermission(permissions.readWatchlist) |
||||
|
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) |
||||
|
@UseInterceptors(TransformDataSourceInResponseInterceptor) |
||||
|
public async getWatchlistItems(): Promise<AssetProfileIdentifier[]> { |
||||
|
return this.watchlistService.getWatchlistItems(this.request.user.id); |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
import { TransformDataSourceInRequestModule } from '@ghostfolio/api/interceptors/transform-data-source-in-request/transform-data-source-in-request.module'; |
||||
|
import { TransformDataSourceInResponseModule } from '@ghostfolio/api/interceptors/transform-data-source-in-response/transform-data-source-in-response.module'; |
||||
|
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module'; |
||||
|
|
||||
|
import { Module } from '@nestjs/common'; |
||||
|
|
||||
|
import { WatchlistController } from './watchlist.controller'; |
||||
|
import { WatchlistService } from './watchlist.service'; |
||||
|
|
||||
|
@Module({ |
||||
|
controllers: [WatchlistController], |
||||
|
imports: [ |
||||
|
PrismaModule, |
||||
|
TransformDataSourceInRequestModule, |
||||
|
TransformDataSourceInResponseModule |
||||
|
], |
||||
|
providers: [WatchlistService] |
||||
|
}) |
||||
|
export class WatchlistModule {} |
@ -0,0 +1,79 @@ |
|||||
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
||||
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; |
||||
|
|
||||
|
import { Injectable, NotFoundException } from '@nestjs/common'; |
||||
|
import { DataSource } from '@prisma/client'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class WatchlistService { |
||||
|
public constructor(private readonly prismaService: PrismaService) {} |
||||
|
|
||||
|
public async createWatchlistItem({ |
||||
|
dataSource, |
||||
|
symbol, |
||||
|
userId |
||||
|
}: { |
||||
|
dataSource: DataSource; |
||||
|
symbol: string; |
||||
|
userId: string; |
||||
|
}): Promise<void> { |
||||
|
const symbolProfile = await this.prismaService.symbolProfile.findUnique({ |
||||
|
where: { |
||||
|
dataSource_symbol: { dataSource, symbol } |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
if (!symbolProfile) { |
||||
|
throw new NotFoundException( |
||||
|
`Asset profile not found for ${symbol} (${dataSource})` |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
await this.prismaService.user.update({ |
||||
|
data: { |
||||
|
watchlist: { |
||||
|
connect: { |
||||
|
dataSource_symbol: { dataSource, symbol } |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
where: { id: userId } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async deleteWatchlistItem({ |
||||
|
dataSource, |
||||
|
symbol, |
||||
|
userId |
||||
|
}: { |
||||
|
dataSource: DataSource; |
||||
|
symbol: string; |
||||
|
userId: string; |
||||
|
}) { |
||||
|
await this.prismaService.user.update({ |
||||
|
data: { |
||||
|
watchlist: { |
||||
|
disconnect: { |
||||
|
dataSource_symbol: { dataSource, symbol } |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
where: { id: userId } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async getWatchlistItems( |
||||
|
userId: string |
||||
|
): Promise<AssetProfileIdentifier[]> { |
||||
|
const user = await this.prismaService.user.findUnique({ |
||||
|
select: { |
||||
|
watchlist: { |
||||
|
select: { dataSource: true, symbol: true } |
||||
|
} |
||||
|
}, |
||||
|
where: { id: userId } |
||||
|
}); |
||||
|
|
||||
|
return user.watchlist ?? []; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue