Skip to content

Commit

Permalink
Merge pull request #93 from boostcampwm-2024/feature/api/stockList-#57
Browse files Browse the repository at this point in the history
[BE] 5.03 주식 리스트 가져오기 API 구현
  • Loading branch information
jinddings authored Nov 13, 2024
2 parents 26855b9 + 100962d commit 10476c6
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 0 deletions.
2 changes: 2 additions & 0 deletions BE/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { SocketModule } from './websocket/socket.module';
import { StockOrderModule } from './stock/order/stock-order.module';
import { StockDetailModule } from './stock/detail/stock-detail.module';
import { typeOrmConfig } from './configs/typeorm.config';
import { StockListModule } from './stock/list/stock-list.module';
import { StockTradeHistoryModule } from './stock/trade/history/stock-trade-history.module';

@Module({
Expand All @@ -26,6 +27,7 @@ import { StockTradeHistoryModule } from './stock/trade/history/stock-trade-histo
SocketModule,
StockDetailModule,
StockOrderModule,
StockListModule,
StockTradeHistoryModule,
],
controllers: [AppController],
Expand Down
18 changes: 18 additions & 0 deletions BE/src/stock/list/dto/stock-list-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';

export class StockListResponseDto {
@ApiProperty({ example: '005930', description: '종목 코드' })
code: string;

@ApiProperty({ example: '삼성전자', description: '종목 이름' })
name: string;

@ApiProperty({ example: 'KOSPI', description: '시장' })
market: string;

constructor(code: string, name: string, market: string) {
this.code = code;
this.name = name;
this.market = market;
}
}
5 changes: 5 additions & 0 deletions BE/src/stock/list/interface/search-params.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface SearchParams {
name?: string;
market?: string;
code?: string;
}
53 changes: 53 additions & 0 deletions BE/src/stock/list/stock-list.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Controller, Get, Query } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { StockListService } from './stock-list.service';
import { StockListResponseDto } from './dto/stock-list-response.dto';

@ApiTags('주식 리스트 API')
@Controller('/api/stocks/list')
export class StockListController {
constructor(private readonly stockListService: StockListService) {}

@ApiOperation({
summary: '전체 주식 종목 조회 API',
description: '모든 주식 종목 리스트를 조회한다.',
})
@Get()
async findAll(): Promise<StockListResponseDto[]> {
return this.stockListService.findAll();
}

@ApiOperation({
summary: '주식 목록 검색 API',
description:
'주식 종목을 검색한다. name, market, code로 검색을 진행할 수 있다.',
})
@ApiResponse({
status: 200,
description: '주식 검색 성공',
type: StockListResponseDto,
isArray: true,
})
@Get('/search')
async searchWithQuery(
@Query('name') name?: string,
@Query('market') market?: string,
@Query('code') code?: string,
): Promise<StockListResponseDto[]> {
return this.stockListService.search({ name, market, code });
}

@ApiOperation({
summary: '특정 주식 종목 조회 API',
description: 'code를 이용해 특정 주식 정보를 조회한다.',
})
@ApiResponse({
status: 200,
description: 'code를 이용한 주식 조회 성공',
type: StockListResponseDto,
})
@Get('/:code')
async findOne(@Query('code') code: string): Promise<StockListResponseDto> {
return this.stockListService.findOne(code);
}
}
13 changes: 13 additions & 0 deletions BE/src/stock/list/stock-list.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BaseEntity, Column, Entity, PrimaryColumn } from 'typeorm';

@Entity()
export class Stocks extends BaseEntity {
@PrimaryColumn()
code: string;

@Column()
name: string;

@Column()
market: string;
}
14 changes: 14 additions & 0 deletions BE/src/stock/list/stock-list.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StockListRepository } from './stock-list.repostiory';
import { StockListService } from './stock-list.service';
import { StockListController } from './stock-list.controller';
import { Stocks } from './stock-list.entity';

@Module({
imports: [TypeOrmModule.forFeature([Stocks])],
controllers: [StockListController],
providers: [StockListRepository, StockListService],
exports: [],
})
export class StockListModule {}
36 changes: 36 additions & 0 deletions BE/src/stock/list/stock-list.repostiory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { Stocks } from './stock-list.entity';
import { SearchParams } from './interface/search-params.interface';

@Injectable()
export class StockListRepository extends Repository<Stocks> {
constructor(@InjectDataSource() dataSource: DataSource) {
super(Stocks, dataSource.createEntityManager());
}

async findAllStocks() {
return this.find();
}

async findOneStock(code: string): Promise<Stocks> {
return this.findOne({ where: { code } });
}

async search(params: SearchParams): Promise<Stocks[]> {
const queryBuilder = this.createQueryBuilder();
if (params.name) {
queryBuilder.where('name LIKE :name', { name: `%${params.name}%` });
}
if (params.market) {
queryBuilder.andWhere('market LIKE :market', {
market: `%${params.market}%`,
});
}
if (params.code) {
queryBuilder.andWhere('code LIKE :code', { code: `%${params.code}%` });
}
return queryBuilder.getMany();
}
}
33 changes: 33 additions & 0 deletions BE/src/stock/list/stock-list.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { StockListRepository } from './stock-list.repostiory';
import { Stocks } from './stock-list.entity';
import { StockListResponseDto } from './dto/stock-list-response.dto';
import { SearchParams } from './interface/search-params.interface';

@Injectable()
export class StockListService {
constructor(private readonly stockListRepository: StockListRepository) {}

private toResponseDto(stock: Stocks): StockListResponseDto {
return new StockListResponseDto(stock.code, stock.name, stock.market);
}

async findAll() {
const stocks = await this.stockListRepository.findAllStocks();
return stocks.map((stock) => this.toResponseDto(stock));
}

async findOne(code: string) {
const stock = await this.stockListRepository.findOneStock(code);

if (!stock) {
throw new NotFoundException(`Stock with code ${code} not found`);
}
return this.toResponseDto(stock);
}

async search(params: SearchParams): Promise<StockListResponseDto[]> {
const stocks = await this.stockListRepository.search(params);
return stocks.map((stock) => this.toResponseDto(stock));
}
}
46 changes: 46 additions & 0 deletions scripts/stock-list.script.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import FinanceDataReader as fdr
import json
import os
import mysql.connector
from dotenv import load_dotenv
from pathlib import Path

root_dir = Path(__file__).parent
env_path = os.path.join(root_dir, '.env')
# .env 파일 로드
load_dotenv(env_path)

db_config = {
'host' : os.getenv('DB_HOST'),
'user' : os.getenv('DB_USERNAME'),
'password' : os.getenv('DB_PASSWD'),
'database' : os.getenv('DB_DATABASE'),
}

def insert_stocks(stockData) :
try :
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor()

insert_query = """INSERT INTO stocks (code, name, market) VALUES (%s, %s, %s)
ON DUPLICATE KEY UPDATE name = VALUES(name), market = VALUES(market)"""

records = stockData.to_dict('records')
for record in records:
values = (record['Code'], record['Name'], record['Market'])
cursor.execute(insert_query, values)
conn.commit()

except Exception as e :
print(e)
conn.rollback()

finally :
if conn.is_connected() :
cursor.close()
conn.close()

df_krx = fdr.StockListing('KRX')
df_selected = df_krx[['Code','Name','Market']]

insert_stocks(df_selected)

0 comments on commit 10476c6

Please sign in to comment.