Skip to content

Instantly share code, notes, and snippets.

@abiriadev
Last active August 2, 2025 01:14
Show Gist options
  • Save abiriadev/612ac1240fbec18d15261db2cf988ffa to your computer and use it in GitHub Desktop.
Save abiriadev/612ac1240fbec18d15261db2cf988ffa to your computer and use it in GitHub Desktop.
const History = require('../schemas/history')
const { HistoryTypes } = require('../utils/types')
const editTypeKeys = ['Create', 'Modify', 'Delete', 'Move', 'ACL', 'Revert']
const rangeMap = {
all: '전체',
'1m': '1달',
'2w': '2주',
'1w': '1주',
'3d': '3일',
'1d': '하루',
}
const rangeToDate = r => {
const n = Date.now()
const d1 = 24 * 60 * 60 * 1000
// prettier-ignore
const d = r === 'all' ? n
: r === '1m' ? d1 * 30
: r === '2w' ? d1 * 14
: r === '1w' ? d1 * 7
: r === '3d' ? d1 * 3
: d1 // 1d
return new Date(n - d)
}
module.exports = {
type: 'page',
menus: {
config: [
{
l: '/admin/contrib-stat',
t: '문서 기여 통계',
},
],
},
url: '/admin/contrib-stat',
async handler(req, res) {
if (!req.permissions.includes('admin')) res.error('권한이 부족합니다.')
const range = req.query.range || 'all'
if (!Object.keys(rangeMap).includes(range))
res.error('유효하지 않은 범위입니다.')
let editTypes = req.query.editTypes || editTypeKeys
if (!Array.isArray(editTypes)) editTypes = [editTypes]
if (!editTypes.every(type => editTypeKeys.includes(type)))
res.error('유효하지 않은 편집 유형이 포함되어 있습니다.')
let mode = req.query.mode
if (mode && mode !== 'diffLen') res.error('유효하지 않은 집계 기준입니다.')
let excludeHidden = !!req.query.excludeHidden
let excludeDeletedUsers = !!req.query.excludeDeletedUsers
const d = await History.aggregate([
{
$lookup: {
from: 'users',
localField: 'user',
foreignField: 'uuid',
as: 'userDetails',
},
},
{
$match: {
createdAt: {
$gte: rangeToDate(range),
},
type: {
$in: editTypes.map(type => HistoryTypes[type]),
},
...(excludeHidden
? {
troll: { $eq: false },
hideLog: { $eq: false },
hidden: { $eq: false },
}
: {}),
...(excludeDeletedUsers ? { userDetails: { $ne: [] } } : {}),
},
},
{
$group: {
_id: '$user',
totalContributions:
mode === 'diffLen'
? {
$sum: '$diffLength',
}
: {
$count: {},
},
userDetails: { $first: '$userDetails' },
},
},
{
$sort: { totalContributions: -1 },
},
{
$limit: 100,
},
{
$project: {
_id: 0,
uuid: '$_id',
totalContributions: 1,
name: { $arrayElemAt: ['$userDetails.name', 0] },
ip: { $arrayElemAt: ['$userDetails.ip', 0] },
},
},
])
res.renderSkin('문서 기여 통계', {
contentHtml: `
<div class="wiki-content">
<p class="wiki-paragraph">최대 100명만 보여줍니다.</p>
<form class="search-form flex box search-form">
<div class="button-block">
<select name="range">${Object.entries(rangeMap)
.map(
([k, v]) =>
`<option value="${k}" ${k === range ? 'selected' : ''}>${v}</option>`,
)
.join('')}</select>
<!-- <input class="input search-input" placeholder="" /> -->
<div style="display: inline-flex">
${editTypeKeys
.map(
type => `
<label>
<input type="checkbox" name="editTypes" value="${type}" ${editTypes.includes(type) ? 'checked' : ''} />
${type}
</label>
`,
)
.join('')}
</div>
<label>
<input type="checkbox" name="excludeHidden" value="true" ${excludeHidden ? 'checked' : ''} />
숨겨진 리비전 또는 반달로 표시된 리비전 또는 편집 요약이 숨겨진 리비전을 집계에서 제외
</label>
<label>
<input type="checkbox" name="excludeDeletedUsers" value="true" ${excludeDeletedUsers ? 'checked' : ''} />
삭제된 사용자 제외
</label>
<label>
<input type="checkbox" name="mode" value="diffLen" ${mode === 'diffLen' ? 'checked' : ''} />
기여 글자수를 기준으로 집계
</label>
<button type="submit">검색</button>
</div>
</form>
<div class="wiki-table-wrap">
<table class="wiki-table">
<thead><tr>${['순위', '총 기여', '사용자']
.map(
th => `<td style="text-align: center;"><strong>${th}</strong></td>`,
)
.join('')}</tr></thead>
<tbody>${d
.map(
({ uuid, name, ip, totalContributions }, i) => `<tr>
<td>${i + 1}</td>
<td><a href="/contribution/${uuid}/document">${totalContributions}</td>
<td>${name ? `<a href="/w/사용자:${name}">${name}</a>` : ip ? `<a href="/contribution/${uuid}/document" style="color: green;">${ip}</a>` : `<a href="/w/삭제된사용자:${uuid}" style="color: gray;">삭제된사용자:${uuid}</a>`}</td>
</tr>`,
)
.join('')}</tbody>
</table>
</div>
</div>`,
})
},
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment