178 lines
4.3 KiB
JavaScript
178 lines
4.3 KiB
JavaScript
// src/controllers/usage.controller.js
|
|
// 사용량 컨트롤러
|
|
|
|
const { UsageLog, User } = require('../models');
|
|
const { Op } = require('sequelize');
|
|
|
|
/**
|
|
* 사용량 요약 조회
|
|
*/
|
|
exports.getSummary = async (req, res, next) => {
|
|
try {
|
|
const userId = req.user.userId;
|
|
|
|
// 사용자 정보 조회
|
|
const user = await User.findByPk(userId);
|
|
if (!user) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
error: {
|
|
code: 'USER_NOT_FOUND',
|
|
message: '사용자를 찾을 수 없습니다.',
|
|
},
|
|
});
|
|
}
|
|
|
|
// 이번 달 사용량
|
|
const now = new Date();
|
|
const monthlyUsage = await UsageLog.getMonthlyTotalByUser(
|
|
userId,
|
|
now.getFullYear(),
|
|
now.getMonth() + 1
|
|
);
|
|
|
|
// 오늘 사용량
|
|
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const todayEnd = new Date(todayStart);
|
|
todayEnd.setDate(todayEnd.getDate() + 1);
|
|
|
|
const todayUsage = await UsageLog.findOne({
|
|
where: {
|
|
userId,
|
|
createdAt: {
|
|
[Op.between]: [todayStart, todayEnd],
|
|
},
|
|
},
|
|
attributes: [
|
|
[UsageLog.sequelize.fn('SUM', UsageLog.sequelize.col('total_tokens')), 'totalTokens'],
|
|
[UsageLog.sequelize.fn('SUM', UsageLog.sequelize.col('cost_usd')), 'totalCost'],
|
|
[UsageLog.sequelize.fn('COUNT', UsageLog.sequelize.col('id')), 'requestCount'],
|
|
],
|
|
raw: true,
|
|
});
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
plan: user.plan,
|
|
limit: {
|
|
monthly: user.monthlyTokenLimit,
|
|
remaining: Math.max(0, user.monthlyTokenLimit - monthlyUsage.totalTokens),
|
|
},
|
|
usage: {
|
|
today: {
|
|
tokens: parseInt(todayUsage?.totalTokens, 10) || 0,
|
|
cost: parseFloat(todayUsage?.totalCost) || 0,
|
|
requests: parseInt(todayUsage?.requestCount, 10) || 0,
|
|
},
|
|
monthly: monthlyUsage,
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 일별 사용량 조회
|
|
*/
|
|
exports.getDailyUsage = async (req, res, next) => {
|
|
try {
|
|
const userId = req.user.userId;
|
|
const { startDate, endDate } = req.query;
|
|
|
|
// 기본값: 최근 30일
|
|
const end = endDate ? new Date(endDate) : new Date();
|
|
const start = startDate ? new Date(startDate) : new Date(end);
|
|
if (!startDate) {
|
|
start.setDate(start.getDate() - 30);
|
|
}
|
|
|
|
const dailyUsage = await UsageLog.getDailyUsageByUser(userId, start, end);
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
startDate: start.toISOString().split('T')[0],
|
|
endDate: end.toISOString().split('T')[0],
|
|
usage: dailyUsage,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 월별 사용량 조회
|
|
*/
|
|
exports.getMonthlyUsage = async (req, res, next) => {
|
|
try {
|
|
const userId = req.user.userId;
|
|
const now = new Date();
|
|
const year = parseInt(req.query.year, 10) || now.getFullYear();
|
|
const month = parseInt(req.query.month, 10) || (now.getMonth() + 1);
|
|
|
|
const monthlyUsage = await UsageLog.getMonthlyTotalByUser(userId, year, month);
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
year,
|
|
month,
|
|
usage: monthlyUsage,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 사용량 로그 목록 조회
|
|
*/
|
|
exports.getLogs = async (req, res, next) => {
|
|
try {
|
|
const userId = req.user.userId;
|
|
const page = parseInt(req.query.page, 10) || 1;
|
|
const limit = parseInt(req.query.limit, 10) || 20;
|
|
const offset = (page - 1) * limit;
|
|
|
|
const { count, rows: logs } = await UsageLog.findAndCountAll({
|
|
where: { userId },
|
|
attributes: [
|
|
'id',
|
|
'providerName',
|
|
'modelName',
|
|
'promptTokens',
|
|
'completionTokens',
|
|
'totalTokens',
|
|
'costUsd',
|
|
'responseTimeMs',
|
|
'success',
|
|
'errorMessage',
|
|
'createdAt',
|
|
],
|
|
order: [['createdAt', 'DESC']],
|
|
limit,
|
|
offset,
|
|
});
|
|
|
|
return res.json({
|
|
success: true,
|
|
data: {
|
|
logs,
|
|
pagination: {
|
|
total: count,
|
|
page,
|
|
limit,
|
|
totalPages: Math.ceil(count / limit),
|
|
},
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return next(error);
|
|
}
|
|
};
|