Skip to content

Commit cc7e25f

Browse files
[24.12.15 / TASK-43] feature: user event tracking (#7)
* feature: database connection 추가 * refactor: db.comfig.ts -> db.config.ts 오타 수정 * feature: pre-commit 기능추가, 변경된 파일만 해당 * feature: add cors * modify: Type folder changed from camelCase to snake_case * refactor: changed TypeScript runtime executor from ts-node to tsx * feature: 간단한 토큰 검증 후 Velog API 호출 * modify: 오타 및 상대 경로 -> 절대 경로 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * modify: user.controller.ts 절대 경로 -> 상대 경로 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor: Token 추출 로직 개선 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor: cors 설정 및 개발 환경변수 추가 * refactor: cors 설정 오탈자 수정 * refactor: src/app.ts 미들웨어 순서 조정 * featrue: 유저 저장 및 업데이트 * modify: .env.sample 수정 * modify: 에러 핸들링 next제거 express.d.ts 수정 * modify src/app.ts 에러핸들링 위치 변경 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * modify: 에러 핸들링 미들웨어 * modify: 에러 핸들링 미들웨어 * feature: login 요쳥 시 token 인증 및 사용자 저장, 업데이트 * modify: query문 수정 Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor: user controller 응답값 통일화 및 에러 미들웨어 수정 * modify: key.util 문서 오타 수정 * refactor: User Repo 에러 핸들링 * modify: repositories 오타 수정 * refactor: user service 에러 헨들링 * modify: 전체 파일 절대경로 -> 상대경로 * refactor: user service 리펙토링 * refactor: dto 검증 미들웨어 try문으로 변경 및 수정 * modify: 로그인 반환값 수정 * refactor: custom error * modify: db error status code Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * modify: import 순서 수정 * feature: log저장 * refactor: User Service encryptTokens Error * refactor: import model path * refactor: error handling * fix: 오탈자 수정 및 경로 수정 * modify: 파일 네이밍 컨벤션 통일화 및 User Tracking Interface 추가 * modify: 기본적인 tracking setting * refactor: validate middleware 유동적으로 검증할 수 있게 수정 * feature: user tracking 저장 * modify: api docs 기반 end point 수정 * refactor: tracking controller 디테일 수정 * refactor: index.ts를 통해 깔끔하게 import * modify: back-office 와 맞게 에러 message 수정 * refactor: supabase에 맞게 수정 * feature: 체류시간 저장 * fix: database value 수정 및 docker-conpose 삭제 * refactor: 유효성 검증 추가 * refactor: router index 추가 * modify: 오타 수정 --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 11785cd commit cc7e25f

31 files changed

+355
-75
lines changed

.env.sample

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
PORT=8080
2-
DB_USER="postgres"
3-
DB_PASSWORD="your_password"
4-
DB_HOST="localhost"
5-
DB_PORT=5432
6-
DB_NAME="your_db_name"
2+
3+
DATABASE_NAME=""
4+
POSTGRES_USER=""
5+
POSTGRES_PASSWORD=""
6+
POSTGRES_HOST=""
7+
POSTGRES_PORT=5432
78
AES_KEY_0="13ccb93c17a8d6e49ba3c5d91e3a6f45"
89
AES_KEY_1="76e2a34bf23cd45876bc91e6a87d3f22"
910
AES_KEY_2="93a4d7e6b34ac8f1092fd5e87a93bc56"

src/app.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import express, { Application } from 'express';
33
import dotenv from 'dotenv';
44
import cors from 'cors';
55
import cookieParser from 'cookie-parser';
6-
import router from './routes/user.router';
7-
import { errorHandlingMiddleware } from './middlewares/error-handling.middleware';
6+
import router from './routes';
7+
import { errorHandlingMiddleware } from './middlewares/errorHandling.middleware';
88

99
dotenv.config();
1010

@@ -21,7 +21,7 @@ app.use(
2121
credentials: true,
2222
}),
2323
);
24-
app.use('/', router);
24+
app.use('/api', router);
2525
app.get('/', (req, res) => {
2626
res.send('Hello, V.D.!');
2727
});

src/configs/db.config.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
import dotenv from 'dotenv';
22
import pg from 'pg';
3+
import logger from './logger.config';
34
// eslint-disable-next-line @typescript-eslint/naming-convention
45
const { Pool } = pg;
56

67
dotenv.config();
78

89
const pool = new Pool({
9-
user: process.env.DB_USER,
10-
host: process.env.DB_HOST,
11-
database: process.env.DB_NAME,
12-
password: process.env.DB_PASSWORD,
13-
port: Number(process.env.DB_PORT),
10+
user: process.env.POSTGRES_USER,
11+
host: process.env.POSTGRES_HOST,
12+
database: process.env.DATABASE_NAME,
13+
password: process.env.POSTGRES_PASSWORD,
14+
port: Number(process.env.POSTGRES_PORT),
15+
ssl: {
16+
rejectUnauthorized: false,
17+
},
1418
});
1519

20+
// timescaleDB 확장. 최초 1회 이므로 즉시실행 함수로
21+
(async () => {
22+
const client = await pool.connect();
23+
try {
24+
await client.query('CREATE EXTENSION IF NOT EXISTS timescaledb;');
25+
logger.info('TimescaleDB 확장 성공');
26+
} catch (error) {
27+
logger.error('TimescaleDB 초기화 실패 : ', error);
28+
} finally {
29+
client.release();
30+
}
31+
})();
1632
export default pool;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { NextFunction, Request, RequestHandler, Response } from 'express';
2+
import logger from '../configs/logger.config';
3+
import { TrackingService } from '../services/tracking.service';
4+
5+
export class TrackingController {
6+
constructor(private trackingService: TrackingService) {}
7+
8+
event = (async (req: Request, res: Response, next: NextFunction) => {
9+
try {
10+
const { type } = req.body;
11+
const { id } = req.user;
12+
13+
await this.trackingService.tracking(type, id);
14+
return res.status(200).json({ success: true, message: '이벤트 데이터 저장완료' });
15+
} catch (error) {
16+
logger.error('user tracking 실패 : ', error);
17+
next(error);
18+
}
19+
}) as RequestHandler;
20+
21+
stay = (async (req: Request, res: Response, next: NextFunction) => {
22+
try {
23+
const { loadDate, unloadDate } = req.body;
24+
const { id } = req.user;
25+
26+
await this.trackingService.stay({ loadDate, unloadDate }, id);
27+
return res.status(200).json({ success: true, message: '체류시간 데이터 완료' });
28+
} catch (error) {
29+
logger.error('user stay time 저장 실패 : ', error);
30+
next(error);
31+
}
32+
}) as RequestHandler;
33+
}

src/controllers/user.controller.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NextFunction, Request, Response, RequestHandler } from 'express';
22
import logger from '../configs/logger.config';
3-
import { UserWithTokenDto } from '../types/dto/user-with-token.dto';
3+
import { UserWithTokenDto } from '../types';
44
import { UserService } from '../services/user.service';
55

66
export class UserController {
@@ -19,7 +19,7 @@ export class UserController {
1919
data: { id: isExistUser.id, email: isExistUser.email, profile },
2020
});
2121
} catch (error) {
22-
logger.error('로그인 실패', error);
22+
logger.error('로그인 실패 : ', error);
2323
next(error);
2424
}
2525
}) as RequestHandler;

src/exception/badRequest.exception.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { CustomError } from './custom.exception';
2+
3+
export class BadRequestError extends CustomError {
4+
constructor(message: string, code: string = 'INVALID_SYNTAX') {
5+
super(message, code, 400);
6+
}
7+
}

src/exception/custom.exception.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export class CustomError extends Error {
2-
code: string;
3-
statusCode?: number;
4-
constructor(message: string, code: string, statusCode?: number) {
2+
readonly code: string;
3+
readonly statusCode: number;
4+
constructor(message: string, code: string, statusCode: number) {
55
super(message);
66
this.name = this.constructor.name;
77
this.code = code;

src/exception/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export { CustomError } from './custom.exception';
2+
export { DBError } from './db.exception';
3+
export { TokenError, TokenExpiredError, InvalidTokenError } from './token.exception';
4+
export { UnauthorizedError } from './unauthorized.exception';
5+
export { BadRequestError } from './badRequest.exception';

src/exception/token.exception.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import { CustomError } from './custom.exception';
2+
import { UnauthorizedError } from './unauthorized.exception';
3+
24
export class TokenError extends CustomError {
35
constructor(message: string) {
46
super(message, 'TOKEN_ERROR', 401);
57
}
68
}
9+
10+
// todo : 추후 만료 여부를 위해 token 에러 구체화 필요. 웬만한 인증관련은 unauthorized로 넘기는게 나을듯
11+
export class TokenExpiredError extends UnauthorizedError {
12+
constructor(message = '토큰이 만료되었습니다') {
13+
super(message, 'TOKEN_EXPIRED');
14+
}
15+
}
16+
17+
export class InvalidTokenError extends UnauthorizedError {
18+
constructor(message = '유효하지 않은 토큰입니다') {
19+
super(message, 'INVALID_TOKEN');
20+
}
21+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { CustomError } from './custom.exception';
2+
3+
export class UnauthorizedError extends CustomError {
4+
constructor(message: string, code: string = 'UNAUTHORIZED') {
5+
super(message, code, 401);
6+
}
7+
}

0 commit comments

Comments
 (0)