Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#22 주식 차트 데이터 조회 기능 추가 #163

Merged
merged 17 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions packages/backend/src/stock/decorator/stockData.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable max-lines-per-function */

import { applyDecorators } from '@nestjs/common';
import { ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger';
import { StockDataResponse } from '../dto/stockData.response';

export function ApiGetStockData(summary: string, type: string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 방법으로 swagger 데코레이터를 따로 분리할 수 있네요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

swagger 데코레이터가 길어 져서 ESLint에 걸려서 분리하는 방법을 찾다보니 이런 방법이 있구나 싶었습니다 !

return applyDecorators(
ApiOperation({ summary }),
ApiParam({
name: 'stockId',
type: String,
description: '주식 ID',
example: 'A005930',
}),
ApiQuery({
name: 'lastStartTime',
required: false,
description: '마지막 시작 시간 (ISO 8601 형식)',
example: '2024-04-01T00:00:00.000Z',
type: String,
format: 'date-time',
}),
ApiResponse({
status: 200,
description: `주식의 ${type} 단위 데이터 성공적으로 조회`,
type: StockDataResponse,
}),
ApiResponse({
status: 404,
description: '주식 데이터가 존재하지 않음',
}),
);
}
161 changes: 161 additions & 0 deletions packages/backend/src/stock/domain/stockData.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
OneToOne,
JoinColumn,
} from 'typeorm';
import { Stock } from './stock.entity';

@Entity('stock_minutely')
export class StockMinutely {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typeORM에서 상속을 적용해서 같은 칼럼이 겹치는 여러개의 엔티티의 코드를 줄일 수 있습니다!

Copy link
Collaborator

@swkim12345 swkim12345 Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 이거 만들었는데 빨리 올릴걸... 두번 일하게 만들어 드렸네요 ㅠㅠ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

괜찮습니다 ! 그리고 엔티티 상속은 몰랐었네요 !

@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
close: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
low: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
high: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
open: number;

@Column({ type: 'bigint' })
volume: number;

@Column({ type: 'timestamp', name: 'start_time' })
startTime: Date;

@OneToOne(() => Stock)
@JoinColumn({ name: 'stock_id' })
stock: Stock;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}

@Entity('stock_daily')
export class StockDaily {
@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
close: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
low: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
high: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
open: number;

@Column({ type: 'bigint' })
volume: number;

@Column({ type: 'timestamp', name: 'start_time' })
startTime: Date;

@OneToOne(() => Stock)
@JoinColumn({ name: 'stock_id' })
stock: Stock;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}
@Entity('stock_weekly')
export class StockWeekly {
@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
close: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
low: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
high: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
open: number;

@Column({ type: 'bigint' })
volume: number;

@Column({ type: 'timestamp', name: 'start_time' })
startTime: Date;

@OneToOne(() => Stock)
@JoinColumn({ name: 'stock_id' })
stock: Stock;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}
@Entity('stock_monthly')
export class StockMonthly {
@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
close: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
low: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
high: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
open: number;

@Column({ type: 'bigint' })
volume: number;

@Column({ type: 'timestamp', name: 'start_time' })
startTime: Date;

@OneToOne(() => Stock)
@JoinColumn({ name: 'stock_id' })
stock: Stock;

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}
@Entity('stock_yearly')
export class StockYearly {
@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
close: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
low: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
high: number;

@Column({ type: 'decimal', precision: 15, scale: 2 })
open: number;

@Column({ type: 'bigint' })
volume: number;

@Column({ type: 'timestamp', name: 'start_time' })
startTime: Date;

@OneToOne(() => Stock)
@JoinColumn({ name: 'stock_id' })
stock: Stock;

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이게 1:다 관계가 될 수 있더라고요 ㅠ..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이게 1:N 관계가 될 수가 있군요! 어떤 관계에서 그렇게 될까요?

@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
}
74 changes: 74 additions & 0 deletions packages/backend/src/stock/dto/stockData.response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';

export class PriceDto {
@ApiProperty({
description: '시작 시간',
type: String,
format: 'date-time',
example: '2024-04-01T00:00:00.000Z',
})
startTime: Date;

@ApiProperty({
description: '시가',
example: '121.00',
})
open: number;

@ApiProperty({
description: '고가',
example: '125.00',
})
high: number;

@ApiProperty({
description: '저가',
example: '120.00',
})
low: number;

@ApiProperty({
description: '종가',
example: '123.45',
})
close: number;
}

export class VolumeDto {
@ApiProperty({
description: '시작 시간',
type: String,
format: 'date-time',
example: '2024-04-01T00:00:00.000Z',
})
startTime: Date;

@ApiProperty({
description: '거래량',
example: 1000,
})
volume: number;
}

export class StockDataResponse {
@ApiProperty({
description: '가격 데이터 배열 (날짜 오름차순)',
type: [PriceDto],
})
@Type(() => PriceDto)
priceDtoList: PriceDto[];

@ApiProperty({
description: '거래량 데이터 배열 (날짜 오름차순, 색 포함)',
type: [VolumeDto],
})
@Type(() => VolumeDto)
volumeDtoList: VolumeDto[];

@ApiProperty({
description: '스크롤해서 불러올 수 있는 데이터가 더 존재하는지',
example: true,
})
hasMore: boolean;
}
85 changes: 83 additions & 2 deletions packages/backend/src/stock/stock.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import { Body, Controller, Delete, HttpCode, Post } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
HttpCode,
Param,
Post,
Query,
} from '@nestjs/common';
import { ApiOkResponse, ApiOperation } from '@nestjs/swagger';
import { ApiGetStockData } from './decorator/stockData.decorator';
import { StockService } from './stock.service';
import {
StockDataDailyService,
StockDataMinutelyService,
StockDataMonthlyService,
StockDataWeeklyService,
StockDataYearlyService,
} from './stockData.service';
import { StockViewsResponse } from '@/stock/dto/stock.Response';
import { StockViewRequest } from '@/stock/dto/stockView.request';
import {
Expand All @@ -11,7 +28,14 @@ import { UserStockResponse } from '@/stock/dto/userStock.response';

@Controller('stock')
export class StockController {
constructor(private readonly stockService: StockService) {}
constructor(
private readonly stockService: StockService,
private readonly stockDataMinutelyService: StockDataMinutelyService,
private readonly stockDataDailyService: StockDataDailyService,
private readonly stockDataWeeklyService: StockDataWeeklyService,
private readonly stockDataMonthlyService: StockDataMonthlyService,
private readonly stockDataYearlyService: StockDataYearlyService,
) {}

@HttpCode(200)
@Post('/view')
Expand Down Expand Up @@ -80,4 +104,61 @@ export class StockController {
'사용자 소유 주식을 삭제했습니다.',
);
}

@Get(':stockId/minutely')
@ApiGetStockData('주식 분 단위 데이터 조회 API', '분')
async getStockDataMinutely(
@Param('stockId') stockId: string,
@Query('lastStartTime') lastStartTime?: string,
) {
return this.stockDataMinutelyService.getStockDataMinutely(
stockId,
lastStartTime,
);
}

@Get(':stockId/daily')
@ApiGetStockData('주식 일 단위 데이터 조회 API', '일')
async getStockDataDaily(
@Param('stockId') stockId: string,
@Query('lastStartTime') lastStartTime?: string,
) {
return this.stockDataDailyService.getStockDataDaily(stockId, lastStartTime);
}

@Get(':stockId/weekly')
@ApiGetStockData('주식 주 단위 데이터 조회 API', '주')
async getStockDataWeekly(
@Param('stockId') stockId: string,
@Query('lastStartTime') lastStartTime?: string,
) {
return this.stockDataWeeklyService.getStockDataWeekly(
stockId,
lastStartTime,
);
}

@Get(':stockId/mothly')
@ApiGetStockData('주식 월 단위 데이터 조회 API', '월')
async getStockDataMonthly(
@Param('stockId') stockId: string,
@Query('lastStartTime') lastStartTime?: string,
) {
return this.stockDataMonthlyService.getStockDataMonthly(
stockId,
lastStartTime,
);
}

@Get(':stockId/yearly')
@ApiGetStockData('주식 연 단위 데이터 조회 API', '연')
async getStockDataYearly(
@Param('stockId') stockId: string,
@Query('lastStartTime') lastStartTime?: string,
) {
return this.stockDataYearlyService.getStockDataYearly(
stockId,
lastStartTime,
);
}
}
2 changes: 1 addition & 1 deletion packages/backend/src/stock/stock.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { Server, Socket } from 'socket.io';

@WebSocketGateway({
path: '/stock',
namespace: '/stock/realtime',
})
export class StockGateway {
@WebSocketServer()
Expand Down
Loading