Skip to content

Commit

Permalink
Merge pull request #2 from 0xTxbi/stock-info-service
Browse files Browse the repository at this point in the history
stock info service
  • Loading branch information
0xTxbi authored Dec 8, 2023
2 parents 02439ee + 116fa4c commit e22097c
Show file tree
Hide file tree
Showing 10 changed files with 473 additions and 119 deletions.
4 changes: 2 additions & 2 deletions data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { DataSource } from "typeorm";
import { User } from "./authentication-service/entities/User";
import { NewsData } from "./news-data-service/entities/NewsData";
import { SentimentData } from "./sentiment-analysis-service/entities/SentimentData";
import { Stock } from "./shared/entities/Stock";
import { configDotenv } from "dotenv";
import { StockInfo } from "./stock-info-service/entities/StockInfo";

// load environment variables from .env file
configDotenv();
Expand All @@ -18,7 +18,7 @@ const AppDataSource = new DataSource({
database: process.env.DB_DATABASE || "",
synchronize: true,
logging: true,
entities: [User, NewsData, SentimentData, Stock],
entities: [User, NewsData, SentimentData, StockInfo],
migrations: [],
ssl: {
rejectUnauthorized: false,
Expand Down
11 changes: 11 additions & 0 deletions migrations/1701883838427-StockInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class StockInfo1701883838427 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
}

public async down(queryRunner: QueryRunner): Promise<void> {
}

}
14 changes: 14 additions & 0 deletions migrations/1701883841242-StockInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class StockInfo1701883841242 implements MigrationInterface {
name = 'StockInfo1701883841242'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "stock_info" ("symbol" character varying NOT NULL, "companyName" character varying NOT NULL, "currentPrice" integer NOT NULL, "industry" character varying NOT NULL, "marketCap" character varying NOT NULL, "changePercent" integer, "volume" integer, "peRatio" integer, CONSTRAINT "PK_e070ed33681787dcb207b8a0b22" PRIMARY KEY ("symbol"))`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "stock_info"`);
}

}
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
"type": "commonjs",
"scripts": {
"typeorm": "typeorm-ts-node-commonjs",
"auth-start": "ts-node authentication-service/src/index.ts"
"auth-start": "ts-node authentication-service/src/index.ts",
"stock-info-start": "nodemon --watch . --ext ts --exec ts-node stock-info-service/src/index.ts"
},
"devDependencies": {
"nodemon": "^3.0.2",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
},
Expand All @@ -20,18 +22,17 @@
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.5",
"@types/node": "^20.10.3",
"axios": "^1.6.2",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"jsonwebtoken": "^9.0.2",
"pg": "^8.11.3",
"reflect-metadata": "^0.1.13",
"routing-controllers": "^0.10.4",
"socket.io": "^4.7.2",
"typeorm": "^0.3.17"
}
}
88 changes: 88 additions & 0 deletions stock-info-service/entities/StockInfo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { Entity, PrimaryColumn, Column } from "typeorm";

@Entity()
export class StockInfo {
@PrimaryColumn()
symbol: string;

@Column()
companyName: string;

@Column()
currentPrice: number;

@Column()
industry: string;

@Column()
marketCap: number;

@Column()
market: string;

@Column()
locale: string;

@Column()
primaryExchange: string;

@Column()
type: string;

@Column()
active: boolean;

@Column()
currencyName: string;

@Column()
cik: string;

@Column()
compositeFigi: string;

@Column()
shareClassFigi: string;

@Column()
phoneNumber: string;

@Column()
address: string;

@Column()
description: string;

@Column()
sicCode: string;

@Column()
sicDescription: string;

@Column()
tickerRoot: string;

@Column()
homepageUrl: string;

@Column()
totalEmployees: number;

@Column()
listDate: string;

@Column()
logoUrl: string;

@Column()
iconUrl: string;

@Column()
shareClassSharesOutstanding: number;

@Column()
weightedSharesOutstanding: number;

@Column()
roundLot: number;
}
122 changes: 122 additions & 0 deletions stock-info-service/src/controllers/StockInfoController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { JsonController, Get, Param, QueryParams } from "routing-controllers";
import { StockInfo } from "../../entities/StockInfo";
import axios from "axios";
import * as dotenv from "dotenv";
import { FilteredStock } from "../types";

dotenv.config();

class ApiError extends Error {
constructor(message: string) {
super(message);
this.name = "ApiError";
}
}

@JsonController("/stock")
export class StockInfoController {
// retrieve stock info
@Get("/:stockSymbol")
async getStockInfo(
@Param("stockSymbol") stockSymbol: string
): Promise<StockInfo> {
try {
// retrieve stock information from the financial API
const apiUrl = `${
process.env.POL_FIN_API_URL
}/tickers/${stockSymbol.toUpperCase()}?apiKey=${
process.env.POL_FIN_API_KEY
}`;
const response = await axios.get(apiUrl);

// map the response data to the StockInfo object
const stockInfo: StockInfo = {
symbol: response.data.results.ticker,
companyName: response.data.results.name,
currentPrice: response.data.results.market_cap,
industry: response.data.results.sic_description,
marketCap: response.data.results.market_cap,
market: response.data.results.market,
locale: response.data.results.locale,
primaryExchange:
response.data.results.primary_exchange,
type: response.data.results.type,
active: response.data.results.active,
currencyName:
response.data.results.currency_name,
cik: response.data.results.cik,
compositeFigi:
response.data.results.composite_figi,
shareClassFigi:
response.data.results.share_class_figi,
phoneNumber: response.data.results.phone_number,
address: response.data.results.address.address1,
description: response.data.results.description,
sicCode: response.data.results.sic_code,
sicDescription:
response.data.results.sic_description,
tickerRoot: response.data.results.ticker_root,
homepageUrl: response.data.results.homepage_url,
totalEmployees:
response.data.results.total_employees,
listDate: response.data.results.list_date,
logoUrl: response.data.results.branding
.logo_url,
iconUrl: response.data.results.branding
.icon_url,
shareClassSharesOutstanding:
response.data.results
.share_class_shares_outstanding,
weightedSharesOutstanding:
response.data.results
.weighted_shares_outstanding,
roundLot: response.data.results.round_lot,
};

return stockInfo;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`Failed to retrieve stock information: ${error.message}`
);
}
throw error;
}
}

// search for stock
@Get("/search/:query")
async searchStocks(
@Param("query") query: string
): Promise<{ stocks: FilteredStock[] }> {
try {
// retrieve stock symbols by keyword
const searchApiUrl = `${process.env.TD_FIN_API_URL}/symbol_search?symbol=${query}&outputsize=5&apikey=${process.env.TD_FIN_API_KEY}`;
const response = await axios.get(searchApiUrl);

// filter out stocks whose exchange country isn't the United States
const filteredStocks = response.data.data.filter(
(stock: any) =>
stock.country === "United States"
);

// map the filtered stocks to an array of simplified stock objects
const stocks: FilteredStock[] = filteredStocks.map(
(stock: any) => ({
symbol: stock.symbol,
exchange: stock.exchange,
instrumentName: stock.instrument_name,
})
);

return { stocks };
} catch (error) {
if (axios.isAxiosError(error)) {
throw new ApiError(
`Failed to perform stock search: ${error.message}`
);
}
throw error;
}
}
}
11 changes: 11 additions & 0 deletions stock-info-service/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import "reflect-metadata";
import { createExpressServer } from "routing-controllers";
import { StockInfoController } from "./controllers/StockInfoController";

const app = createExpressServer({
controllers: [StockInfoController],
});

app.listen(3001, () => {
console.log("the Stock Info Service is running on port 3001");
});
5 changes: 5 additions & 0 deletions stock-info-service/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type FilteredStock = {
symbol: string;
exchange: string;
instrumentName: string;
};
6 changes: 3 additions & 3 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"compilerOptions": {
"lib": ["es5", "es6"],
"target": "es5",
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./build",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true
}
},
"exclude": ["node_modules"]
}
Loading

0 comments on commit e22097c

Please sign in to comment.