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

v1.0.0 릴리즈 #103

Merged
merged 42 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c05c20a
🔥expressLoader 불필요한 코드 및 주석 삭제
jjikky Oct 22, 2024
d02476d
♻️ passport 로직 메서드 추출로 가독성 향상
jjikky Oct 22, 2024
40a16a2
♻️ 미들웨어 메서드 추출 #86
jjikky Oct 22, 2024
c56b5f0
🔥 불필요한 주석 삭제
jjikky Oct 22, 2024
5f8d41e
♻️ config 하드코딩된 문자열 상수화 #86
jjikky Oct 22, 2024
7a5ede9
Merge pull request #88 from jjikky/chore/remove
jjikky Oct 22, 2024
bcdd21c
♻️ request 검증 로직 메서드 추출 리팩터링 #90
jjikky Oct 22, 2024
b4fe8ce
🔥 word repository 불필요한 예외처리 구문 삭제 #90
jjikky Oct 22, 2024
72eb328
✨ 비동기 예외처리 유틸 함수 추가 #91
jjikky Oct 22, 2024
33a9817
♻️ 비동기 예외처리 유틸함수 적용 및 리팩터링 #90 #91
jjikky Oct 22, 2024
4815077
Merge pull request #93 from jjikky/refactor/word
jjikky Oct 23, 2024
96cdb6f
refactor: 컨트롤러 로직 가독성 개선 및updateRecentWordIfLogined 메서드 서비스레이어로 위임 #94
jjikky Oct 23, 2024
0b19d3d
refactor: word 도메인 아나미 패턴 제거 #94
jjikky Oct 23, 2024
1d820b5
Merge pull request #95 from jjikky/refactor/word
jjikky Oct 23, 2024
d5e48e6
refactor: 유저 컨트롤러 비동기 예외처리 유틸 함수 적용 #89
jjikky Oct 23, 2024
e56a2c3
refactor: word 도메인 리팩토링에 따른 메서드 추가 및 수정 #89
jjikky Oct 23, 2024
c3bcf4d
refactor: 카카오 로그인 로직 레이어 분리 #89
jjikky Oct 23, 2024
c5b25fc
refactor: 닉네임 이메일 중복확인 컨트롤러 로직 개선 #89
jjikky Oct 23, 2024
b95191a
refactor: 리이슈 로직 레이어 분리 #89
jjikky Oct 23, 2024
9e937b8
refactor: 로그아웃 로직 레이어 분리 #89
jjikky Oct 23, 2024
f032a88
refactor: 유저 삭제 로직 리팩토링 #89
jjikky Oct 23, 2024
fc4e01f
refactor: passport 유저 조회 키 값 변경 #89
jjikky Oct 26, 2024
61ac838
♻️ token발급 로직 분리 #89
jjikky Oct 26, 2024
7fb0aea
♻️ 토큰 발급 로직 분리에 따른 변경사항 적용 #89
jjikky Oct 26, 2024
4e95b01
refactor: 유저 서비스, 리포지토리 로직 전체 리팩토링 #89
jjikky Oct 26, 2024
b6696f9
Merge pull request #96 from jjikky/refacot/user
jjikky Oct 26, 2024
33f3906
✨ API 레이턴시 로깅 미들웨어 추가 및 적용 #98
jjikky Oct 29, 2024
7815e65
✨ feat: mongoose 스키마에 쿼리 레이턴시 로깅 미들웨어 추가 #98
jjikky Oct 29, 2024
91be13b
♻️refactor: 유저 스키마 로깅 예외 처리 #98
jjikky Oct 29, 2024
c5d783f
♻️ refactor: word 스키마 단어명 인덱스 설정 #99
jjikky Oct 29, 2024
ec247f7
➕ chore : node-cron 종속성 추가 #99
jjikky Oct 29, 2024
6e323fb
✨ redis -> mongo 조회수 동기화 크론 작업 추가 #99
jjikky Oct 29, 2024
d628e76
✨ feat: 조회수 캐싱 기능 추가 #99
jjikky Oct 29, 2024
ee6db68
♻️ refactor: 레디스 캐싱 단어 변수명 명확히 변경 #99
jjikky Oct 30, 2024
2c25060
Merge pull request #100 from jjikky/refactor/word-performance
jjikky Oct 30, 2024
075e6d8
chore: gitignore에 데이터 스크립트 파일 추가 #101
jjikky Nov 3, 2024
92abb82
feat : 유저 데이터 생성 파일 추가 #101
jjikky Nov 3, 2024
51808dd
feat : 단어 데이터 생성 파일 추가 #101
jjikky Nov 3, 2024
92b07d0
fix: 잘못지정된 파일 저장 경로 수정 #101
jjikky Nov 3, 2024
5ab0fe7
chore: 데이터 생성 파일 실행 스크립트 추가 #101
jjikky Nov 3, 2024
f0611d3
chore: word model 유니크 필드 가독성 있게 변경
jjikky Nov 3, 2024
95a4318
Merge pull request #102 from jjikky/feat/generate-test-data
jjikky Nov 10, 2024
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,6 @@ dist
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.pnp.*

scripts/data/*
1 change: 1 addition & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const conf = require('./src/common/config/index');
const expressLoader = require('./src/common/modules/express');
const initDB = require('./src/common/modules/mongodb');
const { START_MESSAGE } = require('./src/common/constants/express');
require('./src/common/modules/cron/sync-redis-to-db');

const app = express();

Expand Down
35 changes: 35 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"prettier": "prettier --write --config ./.prettierrc './src/**'"
"prettier": "prettier --write --config ./.prettierrc './src/**'",
"generate:user": "node ./scripts/generate-users-data.js",
"generate:word": "node ./scripts/generate-words-data.js"
},
"author": "",
"license": "ISC",
Expand All @@ -36,6 +38,7 @@
"moment": "^2.30.1",
"mongoose": "^8.4.3",
"morgan": "^1.10.0",
"node-cron": "^3.0.3",
"nodemon": "^3.1.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
Expand Down
50 changes: 50 additions & 0 deletions scripts/generate-users-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const fs = require('fs');

function generateRandomId() {
return Array(24)
.fill(0)
.map(() => Math.floor(Math.random() * 16).toString(16))
.join('');
}

function generateRandomNickname(index) {
return `user${index}`;
}

function generateUser(index) {
const now = new Date().toISOString();
return {
_id: { $oid: generateRandomId() },
deletedAt: null,
nickname: generateRandomNickname(index),
email: `user${index}@example.com`,
password: '$2b$10$NWhqlRrE5W0YMHlnaBi.CODbFjPZzvgT5G993eKoziVzrhH3M6KNC',
role: 'user',
snsId: null,
provider: null,
recentSearches: [],
requests: [],
createdAt: { $date: now },
updatedAt: { $date: now },
__v: 0,
};
}

function generateUsers(count) {
const users = [];
for (let i = 0; i < count; i++) {
users.push(generateUser(i));
}
return users;
}

async function generateAndSaveData(totalCount) {
const userData = generateUsers(totalCount);
const fileName = './scripts/data/test-users-data.json';
fs.writeFileSync(fileName, JSON.stringify(userData, null, 2));

console.log('✅ 10000개의 유저 데이터가 생성되었습니다.');
console.log('📁 파일 위치: users.json');
}

generateAndSaveData(10000).catch(console.error);
58 changes: 58 additions & 0 deletions scripts/generate-words-data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const fs = require('fs');

const prefixes = ['A', 'Ab', 'Ac', 'Ad', 'Af', 'Ag', 'Al', 'Am', 'An', 'Ap', 'Ar', 'As', 'At', 'Au'];
const middles = ['sync', 'pi', 'data', 'net', 'web', 'cloud', 'tech', 'dev', 'sys', 'log'];
const suffixes = ['Service', 'API', 'Hub', 'Lab', 'Flow', 'Base', 'Core', 'Plus', 'Pro', 'Box'];

function generateRandomWord(index) {
const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
const middle = middles[Math.floor(Math.random() * middles.length)];
const suffix = suffixes[Math.floor(Math.random() * suffixes.length)];
return `${prefix}${middle}${suffix}${index}`;
}

function* generateWordBatch(totalCount, batchSize = 1000) {
for (let i = 0; i < totalCount; i += batchSize) {
const batch = [];
const currentBatchSize = Math.min(batchSize, totalCount - i);

for (let j = 0; j < currentBatchSize; j++) {
const word = generateRandomWord(i + j);
batch.push({
word,
awkPron: `어색한_${word}`,
comPron: `일반적인_${word}`,
info: `${word}에 대한 설명입니다. (테스트 데이터 ${i + j + 1})`,
suggestedBy: 'admin',
freq: Math.floor(Math.random() * 100),
});
}
yield batch;
}
}

async function generateAndSaveData(totalCount) {
console.time('Data Generation');
const fileName = './scripts/data/test-words-data.json';
const batchSize = 1000;
let currentBatch = 1;
const totalBatches = Math.ceil(totalCount / batchSize);

fs.writeFileSync(fileName, '[\n', 'utf8');

for (const batch of generateWordBatch(totalCount, batchSize)) {
console.log(`처리 중: ${currentBatch}/${totalBatches} 배치`);

const batchData = batch.map((item) => JSON.stringify(item)).join(',\n');
fs.appendFileSync(fileName, currentBatch === 1 ? batchData : ',\n' + batchData, 'utf8');

currentBatch++;
}

fs.appendFileSync(fileName, '\n]', 'utf8');

console.log(`✅ ${totalCount}개의 테스트 데이터 생성 완료!`);
console.log(`📁 파일 위치: ${fileName}`);
}

generateAndSaveData(100000).catch(console.error);
3 changes: 2 additions & 1 deletion src/common/config/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const dotenv = require('dotenv');
const path = require('path');
const ErrorMessage = require('../constants/error-message');

const envPath = path.join(__dirname, '../../../.env');

Expand Down Expand Up @@ -75,7 +76,7 @@ switch (process.env.NODE_ENV) {
conf.envMode = 'dev';
break;
default:
console.error('NODE_ENV is not set correctly. It should be either production or development');
console.error(ErrorMessage.INVALID_NODE_ENV);
}

module.exports = conf;
1 change: 1 addition & 0 deletions src/common/constants/error-message.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ErrorMessage = Object.freeze({
AUTHOLIZATION: '권한이 없습니다.',
RETRY_LOGIN: '다시 로그인해주세요.',
NOT_AUTHORIZED: '권한이 없습니다.',
INVALID_NODE_ENV: 'NODE_ENV가 올바르게 설정되지 않았습니다. production 또는 development 중 하나여야 합니다.',

// USER
REGISTER_ERROR: '회원가입중 오류가 발생하였습니다.',
Expand Down
38 changes: 38 additions & 0 deletions src/common/modules/cron/sync-redis-to-db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const cron = require('node-cron');
const redisClient = require('../redis');
const mongoose = require('mongoose');
const Word = require('../../../routes/word/word.model');

// NOTE : 매 정각마다 Redis 조회수를 DB에 반영
cron.schedule('0 * * * *', async () => {
try {
const cachedWordsRaw = await redisClient.sendCommand(['ZRANGE', 'popular_words', '0', '-1', 'WITHSCORES']);

const updates = [];
for (let i = 0; i < cachedWordsRaw.length; i += 2) {
const word = cachedWordsRaw[i];
const redisFreq = Number(cachedWordsRaw[i + 1]);

// NOTE : word 스키마의 조회수 증가 미들웨어를 우회하기 위해 Mongoose 대신 MongoDB 드라이버를 사용
const dbWord = await mongoose.connection.collection('words').findOne({ word }, { projection: { freq: 1 } });
const dbFreq = dbWord ? dbWord.freq : 0;

if (redisFreq !== dbFreq) {
updates.push({
updateOne: {
filter: { word },
update: { freq: redisFreq },
},
});
}
}
if (updates.length > 0) {
await Word.bulkWrite(updates);
console.log('✅ Redis 조회수를 DB에 성공적으로 동기화했습니다.');
} else {
console.log('ℹ️ 이미 동기화된 조회수입니다.');
}
} catch (error) {
console.error('❌ Redis와 DB 동기화 중 오류 발생:', error);
}
});
Loading