Skip to content

Commit 37b9633

Browse files
committed
modify: QRLoginToken 라우터, 서비스, 레포 User쪽으로 합치기
1 parent 85b502c commit 37b9633

File tree

12 files changed

+183
-245
lines changed

12 files changed

+183
-245
lines changed

src/controllers/qr.controller.ts

Lines changed: 0 additions & 95 deletions
This file was deleted.

src/controllers/user.controller.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { NextFunction, Request, Response, RequestHandler, CookieOptions } from 'express';
22
import logger from '@/configs/logger.config';
33
import { EmptyResponseDto, LoginResponseDto, UserWithTokenDto } from '@/types';
4+
import { QRLoginTokenResponseDto } from '@/types/dto/responses/qrResponse.type';
45
import { UserService } from '@/services/user.service';
6+
import { InvalidTokenError, TokenExpiredError } from '@/exception/token.exception';
7+
import { NotFoundError } from '@/exception';
8+
9+
type Token32 = string & { __lengthBrand: 10 };
10+
511
export class UserController {
612
constructor(private userService: UserService) { }
713

@@ -102,4 +108,64 @@ export class UserController {
102108

103109
res.status(200).json(response);
104110
};
111+
112+
createToken: RequestHandler = async (
113+
req: Request,
114+
res: Response<QRLoginTokenResponseDto>,
115+
next: NextFunction,
116+
) => {
117+
try {
118+
const user = req.user;
119+
const ip = req.ip ?? '';
120+
const userAgent = req.headers['user-agent'] || '';
121+
122+
const token = await this.userService.create(user.id, ip, userAgent);
123+
const typedToken = token as Token32;
124+
125+
const response = new QRLoginTokenResponseDto(
126+
true,
127+
'QR 토큰 생성 완료',
128+
{ token: typedToken },
129+
null
130+
);
131+
res.status(200).json(response);
132+
} catch (error) {
133+
logger.error('QR 토큰 생성 실패:', error);
134+
next(error);
135+
}
136+
};
137+
138+
getToken: RequestHandler = async (req: Request, res: Response, next: NextFunction) => {
139+
try {
140+
const token = req.query.token as string;
141+
if (!token) {
142+
throw new InvalidTokenError('토큰이 필요합니다.');
143+
}
144+
145+
const found = await this.userService.useToken(token);
146+
if (!found) {
147+
throw new TokenExpiredError();
148+
}
149+
150+
const user = await this.userService.findByVelogUUID(found.user.toString());
151+
if (!user) throw new NotFoundError('유저를 찾을 수 없습니다.');
152+
153+
const { decryptedAccessToken, decryptedRefreshToken } = this.userService.getDecryptedTokens(
154+
user.group_id,
155+
user.access_token,
156+
user.refresh_token
157+
);
158+
159+
res.clearCookie('access_token', this.cookieOption());
160+
res.clearCookie('refresh_token', this.cookieOption());
161+
162+
res.cookie('access_token', decryptedAccessToken, this.cookieOption());
163+
res.cookie('refresh_token', decryptedRefreshToken, this.cookieOption());
164+
165+
res.redirect('/main');
166+
} catch (error) {
167+
logger.error('QR 토큰 로그인 처리 실패', error);
168+
next(error);
169+
}
170+
};
105171
}

src/repositories/__test__/qr.repo.integration.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import dotenv from 'dotenv';
22
import { Pool } from 'pg';
33
import pg from 'pg';
4-
import { QRLoginTokenRepository } from '@/repositories/qr.repository';
4+
import { UserRepository } from '@/repositories/user.repository';
55
import { generateRandomToken } from '@/utils/generateRandomToken.util';
66
import logger from '@/configs/logger.config';
77

@@ -10,7 +10,7 @@ jest.setTimeout(30000);
1010

1111
describe('QRLoginTokenRepository 통합 테스트', () => {
1212
let testPool: Pool;
13-
let repo: QRLoginTokenRepository;
13+
let repo: UserRepository;
1414

1515
const TEST_DATA = {
1616
USER_ID: 1,
@@ -39,7 +39,7 @@ describe('QRLoginTokenRepository 통합 테스트', () => {
3939
await testPool.query('SELECT 1');
4040
logger.info('테스트 DB 연결 성공');
4141

42-
repo = new QRLoginTokenRepository(testPool);
42+
repo = new UserRepository(testPool);
4343
});
4444

4545
afterAll(async () => {

src/repositories/__test__/qr.repo.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { QRLoginTokenRepository } from '@/repositories/qr.repository';
1+
import { UserRepository } from '@/repositories/user.repository';
22
import { DBError } from '@/exception';
33
import { Pool } from 'pg';
44

@@ -7,10 +7,10 @@ const mockPool: Partial<Pool> = {
77
};
88

99
describe('QRLoginTokenRepository', () => {
10-
let repo: QRLoginTokenRepository;
10+
let repo: UserRepository;
1111

1212
beforeEach(() => {
13-
repo = new QRLoginTokenRepository(mockPool as Pool);
13+
repo = new UserRepository(mockPool as Pool);
1414
});
1515

1616
afterEach(() => {

src/repositories/qr.repository.ts

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/repositories/user.repository.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Pool } from 'pg';
22
import logger from '@/configs/logger.config';
33
import { User } from '@/types';
4+
import { QRLoginToken } from "@/types/models/QRLoginToken.type";
45
import { DBError } from '@/exception';
56

67
export class UserRepository {
7-
constructor(private readonly pool: Pool) { }
8+
constructor(private readonly pool: Pool) {}
89

910
async findByUserVelogUUID(uuid: string): Promise<User> {
1011
try {
@@ -91,4 +92,44 @@ export class UserRepository {
9192
throw new DBError('유저 생성 중 문제가 발생했습니다.');
9293
}
9394
}
95+
96+
async createQRLoginToken(token: string, userId: number, ip: string, userAgent: string): Promise<void> {
97+
try {
98+
const query = `
99+
INSERT INTO users_qrlogintoken (token, user_id, created_at, expires_at, is_used, ip_address, user_agent)
100+
VALUES ($1, $2, NOW(), NOW() + INTERVAL '5 minutes', false, $3, $4);
101+
`;
102+
await this.pool.query(query, [token, userId, ip, userAgent]);
103+
} catch (error) {
104+
logger.error('QRLoginToken Repo Create Error : ', error);
105+
throw new DBError('QR 코드 토큰 생성 중 문제가 발생했습니다.');
106+
}
107+
}
108+
109+
async findQRLoginToken(token: string): Promise<QRLoginToken | null> {
110+
try {
111+
const query = `
112+
SELECT *
113+
FROM users_qrlogintoken
114+
WHERE token = $1 AND is_used = false AND expires_at > NOW();
115+
`;
116+
const result = await this.pool.query(query, [token]);
117+
return result.rows[0] ?? null;
118+
} catch (error) {
119+
logger.error('QRLoginToken Repo find QR Code Error : ', error);
120+
throw new DBError('QR 코드 토큰 조회 중 문제가 발생했습니다.');
121+
}
122+
}
123+
124+
async markTokenUsed(token: string): Promise<void> {
125+
try {
126+
const query = `
127+
UPDATE users_qrlogintoken SET is_used = true WHERE token = $1;
128+
`;
129+
await this.pool.query(query, [token]);
130+
} catch (error) {
131+
logger.error('QRLoginToken Repo mark as used Error : ', error);
132+
throw new DBError('QR 코드 사용 처리 중 문제가 발생했습니다.');
133+
}
134+
}
94135
}

src/routes/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import express, { Router } from 'express';
22
import UserRouter from './user.router';
33
import PostRouter from './post.router';
44
import NotiRouter from './noti.router';
5-
import QrRouter from './qr.router';
65
import LeaderboardRouter from './leaderboard.router';
76

87
const router: Router = express.Router();
@@ -14,7 +13,6 @@ router.use('/ping', (req, res) => {
1413
router.use('/', UserRouter);
1514
router.use('/', PostRouter);
1615
router.use('/', NotiRouter);
17-
router.use('/', QrRouter);
1816
router.use('/', LeaderboardRouter);
1917

2018
export default router;

0 commit comments

Comments
 (0)