AdSense 대시보드 요약 정리
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
const ADSENSE_TOKEN_URL = 'https://oauth2.googleapis.com/token'
|
||||
const ADSENSE_API_BASE_URL = 'https://adsense.googleapis.com/v2'
|
||||
const ADSENSE_PAYMENT_UNPAID_SUFFIX = '/payments/unpaid'
|
||||
const ADSENSE_REPORT_METRICS = [
|
||||
'ESTIMATED_EARNINGS',
|
||||
'PAGE_VIEWS',
|
||||
@@ -67,20 +68,31 @@ const addDays = (date, days) => {
|
||||
* @param {Date} date - 기준 날짜
|
||||
* @returns {Date} 이번 달 시작 날짜
|
||||
*/
|
||||
const startOfLocalMonth = (date) => new Date(date.getFullYear(), date.getMonth(), 1)
|
||||
|
||||
/**
|
||||
* 빈 AdSense 지표를 만든다.
|
||||
* @param {string} label - 기간 라벨
|
||||
* @param {string} key - 기간 키
|
||||
* @returns {Object} 빈 지표
|
||||
*/
|
||||
const createEmptyAdsenseMetric = (label) => ({
|
||||
const createEmptyAdsenseMetric = (label, key = '') => ({
|
||||
key,
|
||||
label,
|
||||
estimatedEarnings: 0,
|
||||
pageViews: 0,
|
||||
impressions: 0,
|
||||
clicks: 0,
|
||||
pageViewsRpm: 0
|
||||
pageViewsRpm: 0,
|
||||
currencyCode: ''
|
||||
})
|
||||
|
||||
/**
|
||||
* 빈 AdSense 지급 요약을 만든다.
|
||||
* @returns {{ balanceAmount: string, lastPaymentAmount: string, lastPaymentDate: string }} 지급 요약
|
||||
*/
|
||||
const createEmptyAdsensePaymentSummary = () => ({
|
||||
balanceAmount: '',
|
||||
lastPaymentAmount: '',
|
||||
lastPaymentDate: ''
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -137,26 +149,33 @@ const getAdsenseAccessToken = async (config) => {
|
||||
* AdSense 보고서 URL을 만든다.
|
||||
* @param {Object} options - 보고서 옵션
|
||||
* @param {string} options.accountName - AdSense 계정 이름
|
||||
* @param {Date} options.startDate - 시작일
|
||||
* @param {Date} options.endDate - 종료일
|
||||
* @param {Date} [options.startDate] - 시작일
|
||||
* @param {Date} [options.endDate] - 종료일
|
||||
* @param {string} [options.dateRange] - AdSense 상대 기간
|
||||
* @param {string} options.currencyCode - 통화 코드
|
||||
* @returns {URL} 보고서 URL
|
||||
*/
|
||||
const createAdsenseReportUrl = ({ accountName, startDate, endDate, currencyCode }) => {
|
||||
const createAdsenseReportUrl = ({ accountName, startDate, endDate, dateRange, currencyCode }) => {
|
||||
const url = new URL(`${ADSENSE_API_BASE_URL}/${accountName}/reports:generate`)
|
||||
const start = toGoogleDateFields(startDate)
|
||||
const end = toGoogleDateFields(endDate)
|
||||
|
||||
for (const metric of ADSENSE_REPORT_METRICS) {
|
||||
url.searchParams.append('metrics', metric)
|
||||
}
|
||||
|
||||
url.searchParams.set('startDate.year', start.year)
|
||||
url.searchParams.set('startDate.month', start.month)
|
||||
url.searchParams.set('startDate.day', start.day)
|
||||
url.searchParams.set('endDate.year', end.year)
|
||||
url.searchParams.set('endDate.month', end.month)
|
||||
url.searchParams.set('endDate.day', end.day)
|
||||
if (dateRange) {
|
||||
url.searchParams.set('dateRange', dateRange)
|
||||
} else if (startDate && endDate) {
|
||||
const start = toGoogleDateFields(startDate)
|
||||
const end = toGoogleDateFields(endDate)
|
||||
|
||||
url.searchParams.set('startDate.year', start.year)
|
||||
url.searchParams.set('startDate.month', start.month)
|
||||
url.searchParams.set('startDate.day', start.day)
|
||||
url.searchParams.set('endDate.year', end.year)
|
||||
url.searchParams.set('endDate.month', end.month)
|
||||
url.searchParams.set('endDate.day', end.day)
|
||||
}
|
||||
|
||||
url.searchParams.set('languageCode', 'ko-KR')
|
||||
|
||||
if (currencyCode) {
|
||||
@@ -170,17 +189,20 @@ const createAdsenseReportUrl = ({ accountName, startDate, endDate, currencyCode
|
||||
* AdSense 단일 기간 보고서를 조회한다.
|
||||
* @param {Object} options - 조회 옵션
|
||||
* @param {string} options.label - 기간 라벨
|
||||
* @param {string} options.key - 기간 키
|
||||
* @param {string} options.accessToken - access token
|
||||
* @param {ReturnType<typeof getAdsenseConfig>} options.config - AdSense 설정
|
||||
* @param {Date} options.startDate - 시작일
|
||||
* @param {Date} options.endDate - 종료일
|
||||
* @param {Date} [options.startDate] - 시작일
|
||||
* @param {Date} [options.endDate] - 종료일
|
||||
* @param {string} [options.dateRange] - AdSense 상대 기간
|
||||
* @returns {Promise<Object>} 기간 지표
|
||||
*/
|
||||
const fetchAdsensePeriodReport = async ({ label, accessToken, config, startDate, endDate }) => {
|
||||
const fetchAdsensePeriodReport = async ({ label, key, accessToken, config, startDate, endDate, dateRange }) => {
|
||||
const url = createAdsenseReportUrl({
|
||||
accountName: config.accountName,
|
||||
startDate,
|
||||
endDate,
|
||||
dateRange,
|
||||
currencyCode: config.currencyCode
|
||||
})
|
||||
const response = await fetch(url, {
|
||||
@@ -194,16 +216,17 @@ const fetchAdsensePeriodReport = async ({ label, accessToken, config, startDate,
|
||||
throw new Error(data.error?.message || `AdSense 보고서 조회 실패: ${response.status}`)
|
||||
}
|
||||
|
||||
const row = Array.isArray(data.rows) ? data.rows[0] : null
|
||||
const row = data.totals || (Array.isArray(data.rows) ? data.rows[0] : null)
|
||||
const cells = row && Array.isArray(row.cells) ? row.cells : []
|
||||
const headers = Array.isArray(data.headers) ? data.headers : []
|
||||
const metric = createEmptyAdsenseMetric(label)
|
||||
const metric = createEmptyAdsenseMetric(label, key)
|
||||
|
||||
headers.forEach((header, index) => {
|
||||
const value = toMetricNumber(cells[index]?.value)
|
||||
|
||||
if (header.name === 'ESTIMATED_EARNINGS') {
|
||||
metric.estimatedEarnings = value
|
||||
metric.currencyCode = header.currencyCode || metric.currencyCode
|
||||
return
|
||||
}
|
||||
|
||||
@@ -230,6 +253,48 @@ const fetchAdsensePeriodReport = async ({ label, accessToken, config, startDate,
|
||||
return metric
|
||||
}
|
||||
|
||||
/**
|
||||
* AdSense 지급 요약을 조회한다.
|
||||
* @param {Object} options - 조회 옵션
|
||||
* @param {string} options.accessToken - access token
|
||||
* @param {ReturnType<typeof getAdsenseConfig>} options.config - AdSense 설정
|
||||
* @returns {Promise<ReturnType<typeof createEmptyAdsensePaymentSummary>>} 지급 요약
|
||||
*/
|
||||
const fetchAdsensePaymentSummary = async ({ accessToken, config }) => {
|
||||
const response = await fetch(`${ADSENSE_API_BASE_URL}/${config.accountName}/payments`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`
|
||||
}
|
||||
})
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || `AdSense 지급 정보 조회 실패: ${response.status}`)
|
||||
}
|
||||
|
||||
const payments = Array.isArray(data.payments) ? data.payments : []
|
||||
const unpaid = payments.find((payment) => String(payment.name || '').endsWith(ADSENSE_PAYMENT_UNPAID_SUFFIX))
|
||||
const paid = payments.find((payment) => !String(payment.name || '').includes('/youtube-') && payment.date)
|
||||
|
||||
return {
|
||||
balanceAmount: unpaid?.amount || '',
|
||||
lastPaymentAmount: paid?.amount || '',
|
||||
lastPaymentDate: paid?.date
|
||||
? [paid.date.year, String(paid.date.month).padStart(2, '0'), String(paid.date.day).padStart(2, '0')].join('.')
|
||||
: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AdSense 지표 목록에서 표시 통화 코드를 고른다.
|
||||
* @param {Object[]} periods - 기간 지표 목록
|
||||
* @param {ReturnType<typeof getAdsenseConfig>} config - AdSense 설정
|
||||
* @returns {string} 통화 코드
|
||||
*/
|
||||
const resolveAdsenseCurrencyCode = (periods, config) => {
|
||||
return periods.find((period) => period.currencyCode)?.currencyCode || config.currencyCode || 'USD'
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 대시보드용 AdSense 요약을 조회한다.
|
||||
* @returns {Promise<Object>} AdSense 요약
|
||||
@@ -242,63 +307,79 @@ export const getAdsenseDashboardSummary = async () => {
|
||||
configured: false,
|
||||
ok: false,
|
||||
message: 'AdSense API 환경 변수가 아직 설정되지 않았습니다.',
|
||||
currencyCode: config.currencyCode || 'KRW',
|
||||
currencyCode: config.currencyCode || 'USD',
|
||||
generatedAt: new Date().toISOString(),
|
||||
today: createEmptyAdsenseMetric('오늘'),
|
||||
yesterday: createEmptyAdsenseMetric('어제'),
|
||||
month: createEmptyAdsenseMetric('이번 달')
|
||||
periods: [],
|
||||
balance: createEmptyAdsensePaymentSummary()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const today = startOfLocalDay(new Date())
|
||||
const yesterday = addDays(today, -1)
|
||||
const monthStart = startOfLocalMonth(today)
|
||||
const recent28Start = addDays(today, -27)
|
||||
const accessToken = await getAdsenseAccessToken(config)
|
||||
const [todayMetric, yesterdayMetric, monthMetric] = await Promise.all([
|
||||
const [todayMetric, yesterdayMetric, last7DaysMetric, monthMetric, recent28DaysMetric, balance] = await Promise.all([
|
||||
fetchAdsensePeriodReport({
|
||||
label: '오늘',
|
||||
label: '오늘 현재까지',
|
||||
key: 'today',
|
||||
accessToken,
|
||||
config,
|
||||
startDate: today,
|
||||
endDate: today
|
||||
dateRange: 'TODAY'
|
||||
}),
|
||||
fetchAdsensePeriodReport({
|
||||
label: '어제',
|
||||
key: 'yesterday',
|
||||
accessToken,
|
||||
config,
|
||||
startDate: yesterday,
|
||||
endDate: yesterday
|
||||
dateRange: 'YESTERDAY'
|
||||
}),
|
||||
fetchAdsensePeriodReport({
|
||||
label: '지난 7일',
|
||||
key: 'last7Days',
|
||||
accessToken,
|
||||
config,
|
||||
dateRange: 'LAST_7_DAYS'
|
||||
}),
|
||||
fetchAdsensePeriodReport({
|
||||
label: '이번 달',
|
||||
key: 'month',
|
||||
accessToken,
|
||||
config,
|
||||
startDate: monthStart,
|
||||
dateRange: 'MONTH_TO_DATE'
|
||||
}),
|
||||
fetchAdsensePeriodReport({
|
||||
label: '최근 28일',
|
||||
key: 'recent28Days',
|
||||
accessToken,
|
||||
config,
|
||||
startDate: recent28Start,
|
||||
endDate: today
|
||||
}),
|
||||
fetchAdsensePaymentSummary({
|
||||
accessToken,
|
||||
config
|
||||
})
|
||||
])
|
||||
const periods = [todayMetric, yesterdayMetric, last7DaysMetric, monthMetric, recent28DaysMetric]
|
||||
|
||||
return {
|
||||
configured: true,
|
||||
ok: true,
|
||||
message: '',
|
||||
currencyCode: config.currencyCode || 'KRW',
|
||||
currencyCode: resolveAdsenseCurrencyCode(periods, config),
|
||||
generatedAt: new Date().toISOString(),
|
||||
today: todayMetric,
|
||||
yesterday: yesterdayMetric,
|
||||
month: monthMetric
|
||||
periods,
|
||||
balance
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
configured: true,
|
||||
ok: false,
|
||||
message: error instanceof Error ? error.message : 'AdSense 통계를 불러오지 못했습니다.',
|
||||
currencyCode: config.currencyCode || 'KRW',
|
||||
currencyCode: config.currencyCode || 'USD',
|
||||
generatedAt: new Date().toISOString(),
|
||||
today: createEmptyAdsenseMetric('오늘'),
|
||||
yesterday: createEmptyAdsenseMetric('어제'),
|
||||
month: createEmptyAdsenseMetric('이번 달')
|
||||
periods: [],
|
||||
balance: createEmptyAdsensePaymentSummary()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user