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/deploy #141

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
66 changes: 66 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Deploy Monorepo

on:
push:
branches:
- dev
pull_request:
branches:
- dev

jobs:
build-and-deploy:
runs-on: ubuntu-latest

services:
docker:
image: docker:20.10.7
options: --privileged

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build frontend
run: |
yarn workspace frontend build

- name: Build backend
run: |
yarn workspace backend build

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push frontend Docker image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/frontend:latest -f packages/frontend/Dockerfile .
docker push ${{ secrets.DOCKER_USERNAME }}/frontend:latest

- name: Build and push backend Docker image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/backend:latest -f packages/backend/Dockerfile .
docker push ${{ secrets.DOCKER_USERNAME }}/backend:latest

- name: Deploy to server
uses: appleboy/[email protected]
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull ${{ secrets.DOCKER_USERNAME }}/frontend:latest
docker pull ${{ secrets.DOCKER_USERNAME }}/backend:latest
docker-compose up -d
7 changes: 7 additions & 0 deletions packages/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM node:18-alpine
WORKDIR /packages
COPY . .
RUN yarn install --frozen-lockfile
RUN yarn workspace backend build
EXPOSE 3000
CMD ["yarn", "workspace", "backend", "start:prod"]
8 changes: 7 additions & 1 deletion packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
},
"dependencies": {
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/typeorm": "^10.0.2",
"dotenv": "^16.4.5",
"mysql2": "^3.11.4",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"typeorm": "^0.3.20"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand All @@ -36,6 +41,7 @@
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"cz-emoji-conventional": "^1.1.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
Expand Down
22 changes: 0 additions & 22 deletions packages/backend/src/app.controller.spec.ts

This file was deleted.

16 changes: 0 additions & 16 deletions packages/backend/src/app.controller.ts

This file was deleted.

17 changes: 12 additions & 5 deletions packages/backend/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StockPriceModule } from './stock-price/stock-price.module';
import { OpenapiScraperModule } from './openapi-scraper/openapi-scraper.module';
import { typeormConfig } from '@/configs/typeorm.config';
import { StockModule } from '@/stock/stock.module';

@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
imports: [StockModule,
TypeOrmModule.forRoot(typeormConfig),
OpenapiScraperModule,
StockPriceModule,
],
controllers: [],
providers: [],
})
export class AppModule {}
8 changes: 0 additions & 8 deletions packages/backend/src/app.service.ts

This file was deleted.

15 changes: 15 additions & 0 deletions packages/backend/src/configs/typeorm.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import * as dotenv from 'dotenv';

dotenv.config();

export const typeormConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
logging: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export class DownloadDto {
baseDir!: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class KospiMaster {
@PrimaryGeneratedColumn({ type: 'int', unsigned: true })
id?: number;

@Column()
shortCode?: string;

@Column()
standardCode?: string;

@Column()
koreanName?: string;

@Column()
groupCode?: string;

@Column()
marketCapSize?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { KoreaStockInfoController } from './korea-stock-info.controller';

describe('KoreaStockInfoController', () => {
let controller: KoreaStockInfoController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [KoreaStockInfoController],
}).compile();

controller = module.get<KoreaStockInfoController>(KoreaStockInfoController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { Controller } from '@nestjs/common';

@Controller('korea-stock-info')
export class KoreaStockInfoController {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { KoreaStockInfoService } from './korea-stock-info.service';

describe('KoreaStockInfoService', () => {
let service: KoreaStockInfoService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [KoreaStockInfoService],
}).compile();

service = module.get<KoreaStockInfoService>(KoreaStockInfoService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { Injectable } from '@nestjs/common';
import * as fs from 'fs';
import * as path from 'path';
import * as https from 'https';
import * as readline from 'readline';
import * as iconv from 'iconv-lite';
import * as unzipper from 'unzipper';
import { DownloadDto } from './dto/download.dto';
import { KospiMaster } from './entities/stock.entity';

@Injectable()
export class KoreaStockInfoService {
constructor() {
this.downloadKosdaqMaster({ baseDir: './' });
this.downloadKospiMaster({ baseDir: './' });

this.getKospiMasterData('./');
this.getKosdaqMasterData('./');
}

private async downloadFile(url: string, filePath: string): Promise<void> {
return new Promise((resolve, reject) => {
const file = fs.createWriteStream(filePath);
https
.get(url, (response) => {
response.pipe(file);
file.on('finish', () => {
file.close();
resolve();
});
})
.on('error', (error) => {
fs.unlinkSync(filePath);
reject(error);
});
});
}

private async extractZip(filePath: string, extractTo: string): Promise<void> {
await fs
.createReadStream(filePath)
.pipe(unzipper.Extract({ path: extractTo }))
.promise();
}

public async downloadKosdaqMaster(downloadDto: DownloadDto): Promise<any> {
const { baseDir } = downloadDto;
const fileName = 'kosdaq_code.zip';
const filePath = path.join(baseDir, fileName);
const extractedFile = path.join(baseDir, 'kosdaq_code.mst');

await this.downloadFile(
'https://new.real.download.dws.co.kr/common/master/kosdaq_code.mst.zip',
filePath,
);
await this.extractZip(filePath, baseDir);

fs.unlink(filePath, (err) => {
if (err) throw err;
});

return extractedFile;
}

public async downloadKospiMaster(downloadDto: DownloadDto): Promise<any> {
const { baseDir } = downloadDto;
const fileName = 'kospi_code.zip';
const filePath = path.join(baseDir, fileName);
const extractedFile = path.join(baseDir, 'kospi_code.mst');

await this.downloadFile(
'https://new.real.download.dws.co.kr/common/master/kospi_code.mst.zip',
filePath,
);
await this.extractZip(filePath, baseDir);
fs.unlink(filePath, (err) => {
if (err) throw err;
});

return extractedFile;
}

public async getKospiMasterData(baseDir: string): Promise<KospiMaster[]> {
const fileName = path.join(baseDir, 'kospi_code.mst');
const kospiMasters: KospiMaster[] = [];

const rl = readline.createInterface({
input: fs.createReadStream(fileName).pipe(iconv.decodeStream('cp949')),
crlfDelay: Infinity,
});

for await (const row of rl) {
const shortCode = row.slice(0, 9).trim();
const standardCode = row.slice(9, 21).trim();
const koreanName = row.slice(21, row.length - 228).trim();
const groupCode = row.slice(-228, -226).trim();
const marketCapSize = row.slice(-226, -225).trim();

kospiMasters.push({
shortCode,
standardCode,
koreanName,
groupCode,
marketCapSize,
});
}

console.log('Done');
return kospiMasters;
}

public async getKosdaqMasterData(baseDir: string): Promise<KospiMaster[]> {
const fileName = path.join(baseDir, 'kosdaq_code.mst');
const kosdaqMasters: KospiMaster[] = [];

const rl = readline.createInterface({
input: fs.createReadStream(fileName).pipe(iconv.decodeStream('cp949')),
crlfDelay: Infinity,
});

for await (const row of rl) {
const shortCode = row.slice(0, 9).trim();
const standardCode = row.slice(9, 21).trim();
const koreanName = row.slice(21, row.length - 222).trim();
const groupCode = row.slice(-222, -220).trim();
const marketCapSize = row.slice(-220, -219).trim();

kosdaqMasters.push({
shortCode,
standardCode,
koreanName,
groupCode,
marketCapSize,
});
}

console.log('Done');
return kosdaqMasters;
}
}
Loading
Loading