Merge pull request #3 from Degulleo/DO-22-서버-코인-관리-api-추가

DO-22-서버-코인-관리-api-추가
This commit is contained in:
Fiore 2025-03-12 18:14:09 +09:00 committed by GitHub
commit 85a08b67ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 252 additions and 4 deletions

4
app.js
View File

@ -9,6 +9,7 @@ var MongoClient = mongodb.MongoClient;
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var leaderboardRouter = require('./routes/leaderboard');
var coinsRouter = require('./routes/coins');
const session = require('express-session');
var fileStore = require('session-file-store')(session);
@ -47,7 +48,7 @@ async function connectDB(){
// 연결 종료 처리
process.on("SIGINT", async ()=> {
await database.close();
await client.close();
console.log("Database Connected");
process.exit(0);
})
@ -75,6 +76,7 @@ app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/leaderboard', leaderboardRouter);
app.use('/coins', coinsRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {

246
routes/coins.js Normal file
View File

@ -0,0 +1,246 @@
var express = require('express');
const {ObjectId} = require("mongodb");
var router = express.Router();
// 인증 확인 미들웨어
const isAuthenticated = (req, res, next) => {
if (req.session && req.session.isAuthenticated) {
next();
} else {
res.status(401).json({ result: 'AUTH_REQUIRED', message: '로그인이 필요합니다.' });
}
};
// 코인 잔액 조회
router.get('/', isAuthenticated, async function(req, res) {
try {
const userId = req.session.userId;
const database = req.app.get('database');
const users = database.collection('users');
const user = await users.findOne({ _id: new ObjectId(userId) });
if (!user) {
return res.status(404).json({ result: 'USER_NOT_FOUND', message: '사용자를 찾을 수 없습니다.' });
}
// 코인 필드가 없는 경우 기본값 0으로 설정
const coins = user.coins || 0;
res.json({
result: 'SUCCESS',
coins: coins
});
} catch (err) {
console.log("코인 잔액 조회 중 오류 발생 : ", err);
res.status(500).json({ result: 'ERROR', message: '서버 오류가 발생했습니다.' });
}
});
// 코인 차감 (100 코인)
router.post('/deduct', isAuthenticated, async function(req, res) {
try {
const userId = req.session.userId;
const deductAmount = 100; // 고정 차감 금액
const database = req.app.get('database');
const users = database.collection('users');
// 트랜잭션이 이상적이지만, 간단한 구현을 위해 findOne과 updateOne으로 처리
const user = await users.findOne({ _id: new ObjectId(userId) });
if (!user) {
return res.status(404).json({ result: 'USER_NOT_FOUND', message: '사용자를 찾을 수 없습니다.' });
}
// 코인 필드가 없는 경우 기본값 0으로 설정
const currentCoins = user.coins || 0;
// 코인이 부족한 경우
if (currentCoins < deductAmount) {
return res.status(400).json({
result: 'INSUFFICIENT_COINS',
message: '코인이 부족합니다.',
currentCoins: currentCoins
});
}
// 코인 차감
const updateResult = await users.updateOne(
{ _id: new ObjectId(userId) },
{ $inc: { coins: -deductAmount } }
);
if (updateResult.modifiedCount !== 1) {
return res.status(500).json({ result: 'UPDATE_FAILED', message: '코인 차감에 실패했습니다.' });
}
// 코인 차감 로그
// const coinLogs = database.collection('coinLogs');
// await coinLogs.insertOne({
// userId: new ObjectId(userId),
// type: 'DEDUCT',
// amount: deductAmount,
// timestamp: new Date(),
// balance: currentCoins - deductAmount
// });
res.json({
result: 'SUCCESS',
message: `${deductAmount} 코인이 차감되었습니다.`,
deducted: deductAmount,
remainingCoins: currentCoins - deductAmount
});
} catch (err) {
console.log("코인 차감 중 오류 발생 : ", err);
res.status(500).json({ result: 'ERROR', message: '서버 오류가 발생했습니다.' });
}
});
// 코인 충전 (광고 시청 후 500 코인)
router.post('/recharge/ad', isAuthenticated, async function(req, res) {
try {
const userId = req.session.userId;
const rechargeAmount = 500; // 고정 충전 금액
const database = req.app.get('database');
const users = database.collection('users');
// 광고 시청 완료 여부 확인 (클라이언트에서 전송)
const { adCompleted } = req.body;
if (!adCompleted) {
return res.status(400).json({ result: 'AD_NOT_COMPLETED', message: '광고 시청이 완료되지 않았습니다.' });
}
// 광고 ID 또는 추가 검증 로직이 필요하다면 여기에 구현
// 트랜잭션이 이상적이지만, 간단한 구현을 위해 updateOne으로 처리
const updateResult = await users.updateOne(
{ _id: new ObjectId(userId) },
{ $inc: { coins: rechargeAmount } }
);
if (updateResult.modifiedCount !== 1) {
return res.status(500).json({ result: 'UPDATE_FAILED', message: '코인 충전에 실패했습니다.' });
}
// 업데이트된 사용자 정보 가져오기
const updatedUser = await users.findOne({ _id: new ObjectId(userId) });
const currentCoins = updatedUser.coins || 0;
// 코인 충전 로그
// const coinLogs = database.collection('coinLogs');
// await coinLogs.insertOne({
// userId: new ObjectId(userId),
// type: 'RECHARGE',
// amount: rechargeAmount,
// adId: req.body.adId, // 광고 ID가 있다면
// timestamp: new Date(),
// balance: currentCoins
// });
res.json({
result: 'SUCCESS',
message: `광고 시청으로 ${rechargeAmount} 코인이 충전되었습니다.`,
recharged: rechargeAmount,
currentCoins: currentCoins
});
} catch (err) {
console.log("코인 충전 중 오류 발생 : ", err);
res.status(500).json({ result: 'ERROR', message: '서버 오류가 발생했습니다.' });
}
});
// 구매를 통한 코인 충전 (충전 수량은 파라미터로 전달 받음)
router.post('/purchase', isAuthenticated, async function(req, res) {
try {
const userId = req.session.userId;
const database = req.app.get('database');
const users = database.collection('users');
// 구매 정보 확인
const { amount, paymentId, paymentType } = req.body;
// 필수 파라미터 검증
if (!amount || amount <= 0) {
return res.status(400).json({
result: 'INVALID_PARAMETER',
message: '유효한 충전 수량을 입력해주세요.'
});
}
// 결제 검증
// 이 부분은 실제 결제 검증 로직으로 대체해야 합니다
const paymentVerified = await verifyPayment(paymentId, amount, paymentType);
if (!paymentVerified) {
return res.status(400).json({
result: 'PAYMENT_VERIFICATION_FAILED',
message: '결제 검증에 실패했습니다.'
});
}
// 코인 충전
const updateResult = await users.updateOne(
{ _id: new ObjectId(userId) },
{ $inc: { coins: amount } }
);
if (updateResult.modifiedCount !== 1) {
// 결제는 성공했는데 코인 충전 실패 시 관리자에게 알림 필요
console.error(`사용자 ${userId}의 코인 충전 실패. 결제ID: ${paymentId}`);
return res.status(500).json({ result: 'UPDATE_FAILED', message: '코인 충전에 실패했습니다.' });
}
// 업데이트된 사용자 정보 가져오기
const updatedUser = await users.findOne({ _id: new ObjectId(userId) });
const currentCoins = updatedUser.coins || 0;
// 코인 충전 로그
// const coinLogs = database.collection('coinLogs');
// await coinLogs.insertOne({
// userId: new ObjectId(userId),
// type: 'PURCHASE',
// amount: amount,
// paymentId: paymentId,
// paymentType: paymentType,
// timestamp: new Date(),
// balance: currentCoins
// });
// 결제 내역 저장
// const payments = database.collection('payments');
// await payments.insertOne({
// userId: new ObjectId(userId),
// paymentId: paymentId,
// paymentType: paymentType,
// amount: amount,
// status: 'COMPLETED',
// timestamp: new Date()
// });
res.json({
result: 'SUCCESS',
message: `${amount} 코인이 성공적으로 충전되었습니다.`,
purchased: amount,
currentCoins: currentCoins
});
} catch (err) {
console.log("구매 코인 충전 중 오류 발생 : ", err);
res.status(500).json({ result: 'ERROR', message: '서버 오류가 발생했습니다.' });
}
});
// 결제 검증 함수 (실제 결제 서비스에 맞게 구현 필요)
async function verifyPayment(paymentId, amount, paymentType) {
// 여기에 실제 결제 검증 로직 구현
// 예: 결제 서비스 API 호출하여 결제 상태 확인
// 테스트 목적으로 항상 true 반환 (실제 구현에서는 제거)
return true;
}
module.exports = router;

View File

@ -56,7 +56,7 @@ router.post('/signup', async function (req, res, next) {
score:0,
win:0,
lose:0,
coin: 1000
coins: 1000
});
res.status(201).send("사용자가 성공적으로 생성되었습니다.");
@ -91,14 +91,14 @@ router.post("/signin", async function (req, res, next) {
req.session.profileImageIndex = existingUser.profileImageIndex || 0;
req.session.rating = existingUser.rating;
req.session.score = existingUser.score;
req.session.coin = existingUser.coin;
req.session.coins = existingUser.coins;
res.json({
result: ResponseType.SUCCESS,
imageindex: existingUser.imageindex,
rating: existingUser.rating,
score: existingUser.score,
coin: existingUser.coin,
coins: existingUser.coins,
});
} else {
res.json({result : ResponseType.INVALID_PASSWORD});