From 7a4d382dc99a39e39f99e187ded7c4e35fb65c9a Mon Sep 17 00:00:00 2001 From: masnn Date: Sun, 7 Jun 2020 19:11:19 +0800 Subject: [PATCH] /ranking & script.recalcRanking --- hydro/handler/domain.js | 26 ++++++++++ hydro/lib/rating.js | 4 +- hydro/loader.js | 2 +- hydro/model/document.js | 4 +- hydro/model/domain.js | 2 +- hydro/model/user.js | 94 +++++++++++++++++++----------------- hydro/script/rating.js | 3 +- hydro/script/recalcRating.js | 29 +++++++++++ templates/domain_rank.html | 43 +++++++++++++++++ 9 files changed, 154 insertions(+), 53 deletions(-) create mode 100644 hydro/script/recalcRating.js create mode 100644 templates/domain_rank.html diff --git a/hydro/handler/domain.js b/hydro/handler/domain.js index 5721d0f48..d45b94443 100644 --- a/hydro/handler/domain.js +++ b/hydro/handler/domain.js @@ -1,10 +1,35 @@ const user = require('../model/user'); const domain = require('../model/domain'); const system = require('../model/system'); +const paginate = require('../lib/paginate'); const { Route, Handler } = require('../service/server'); const { PERM_MANAGE } = require('../permission'); const { RoleAlreadyExistError, ValidationError, PermissionError } = require('../error'); +class DomainRankHandler extends Handler { + async get({ domainId, page = 1 }) { + const [dudocs, upcount, ucount] = await paginate( + user.getMultiInDomain(domainId).sort({ rating: -1 }), + page, + 100, + ); + let udocs = []; + // eslint-disable-next-line no-await-in-loop + for (const uid of dudocs.map((dudoc) => dudoc.uid)) { + udocs.push(user.getById(domainId, uid)); + } + udocs = await Promise.all(udocs); + const path = [ + ['Hydro', '/'], + ['domain_rank', null], + ]; + this.response.template = 'domain_rank.html'; + this.response.body = { + udocs, upcount, ucount, page, path, + }; + } +} + class DomainCreateHandler extends Handler { async prepare() { if (this.user.priv !== 1) { @@ -158,6 +183,7 @@ class DomainRoleHandler extends ManageHandler { } async function apply() { + Route('/ranking', DomainRankHandler); Route('/domain/create', DomainCreateHandler); Route('/domain/dashboard', DomainDashboardHandler); Route('/domain/edit', DomainEditHandler); diff --git a/hydro/lib/rating.js b/hydro/lib/rating.js index 585b997d7..d18cea593 100644 --- a/hydro/lib/rating.js +++ b/hydro/lib/rating.js @@ -1,9 +1,9 @@ class User { - constructor(rank, old, handle = 0) { + constructor(rank, old, uid = 0) { this.rank = rank; this.old = old; this.seed = 1; - this.handle = handle.toString(); + this.uid = uid; } } diff --git a/hydro/loader.js b/hydro/loader.js index 8e33a5aeb..9378b92b5 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -272,7 +272,7 @@ async function load() { } } const builtinScript = [ - 'install', 'uninstall', 'rating', + 'install', 'uninstall', 'rating', 'recalcRating', ]; for (const i of builtinScript) require(`./script/${i}`); await script(); diff --git a/hydro/model/document.js b/hydro/model/document.js index 5958fb474..14454aa05 100644 --- a/hydro/model/document.js +++ b/hydro/model/document.js @@ -5,7 +5,7 @@ const db = require('../service/db'); const coll = db.collection('document'); const collStatus = db.collection('document.status'); -const TYPE_DOMAIN_ROLE = 0; +const TYPE_DOMAIN_USER = 0; const TYPE_PROBLEM = 10; const TYPE_PROBLEM_SOLUTION = 11; const TYPE_PROBLEM_LIST = 12; @@ -353,7 +353,7 @@ global.Hydro.model.document = module.exports = { setStatus, setSub, - TYPE_DOMAIN_ROLE, + TYPE_DOMAIN_USER, TYPE_CONTEST, TYPE_DISCUSSION, TYPE_DISCUSSION_NODE, diff --git a/hydro/model/domain.js b/hydro/model/domain.js index 0fe05d4bc..f1fef8305 100644 --- a/hydro/model/domain.js +++ b/hydro/model/domain.js @@ -25,7 +25,7 @@ function add(domainId, owner, name) { tasks.push( document.add( domainId, builtin.BUILTIN_ROLES[id].perm, owner, - document.TYPE_DOMAIN_ROLE, id, + document.TYPE_DOMAIN_USER, id, ), ); } diff --git a/hydro/model/user.js b/hydro/model/user.js index 4d1bbd197..8e0608a38 100644 --- a/hydro/model/user.js +++ b/hydro/model/user.js @@ -9,24 +9,27 @@ const db = require('../service/db'); const coll = db.collection('user'); class USER { - constructor(user) { - this._id = user._id; - this.mail = user.mail; - this.uname = user.uname; - this.salt = () => user.salt; - this.hash = () => user.hash; - this.perm = user.perm; - this.priv = user.priv; - this.role = user.role; - this.viewLang = user.language || 'zh_CN'; - this.codeLang = user.codeLang || 'c'; - this.codeTemplate = user.codeTemplate || ''; - this.regat = user.regat; - this.loginat = user.loginat; - this.bio = user.bio || ''; - this.gravatar = user.gravatar || ''; - this.nAccept = user.nAccept || 0; - this.nSubmit = user.nSubmit || 0; + constructor(udoc, dudoc) { + this.udoc = () => udoc; + this.dudoc = () => dudoc; + this._id = udoc._id; + this.mail = udoc.mail; + this.uname = udoc.uname; + this.salt = () => udoc.salt; + this.hash = () => udoc.hash; + this.priv = udoc.priv; + this.viewLang = udoc.language || 'zh_CN'; + this.codeLang = udoc.codeLang || 'c'; + this.codeTemplate = udoc.codeTemplate || ''; + this.regat = udoc.regat; + this.loginat = udoc.loginat; + this.bio = udoc.bio || ''; + this.gravatar = udoc.gravatar || ''; + this.nAccept = dudoc.nAccept || 0; + this.nSubmit = dudoc.nSubmit || 0; + this.rating = dudoc.rating || 1500; + this.perm = dudoc.perm; + this.role = dudoc.role; } hasPerm(p) { @@ -40,25 +43,20 @@ class USER { } } -async function getPerm(domainId, udoc) { - if (udoc._id === 1) { // Is Guest - const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'guest'); - return p ? p.content : builtin.BUILTIN_ROLES.guest.perm; - } - if (udoc.priv === 1) { // Is superadmin - const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'admin'); - return p ? p.content : builtin.BUILTIN_ROLES.admin.perm; - } - const role = await document.getStatus(domainId, document.TYPE_DOMAIN_ROLE, 0, udoc._id); - const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, (role || {}).role || 'default'); - return p ? p.content : builtin.BUILTIN_ROLES.default.perm; +async function getInDomain(domainId, udoc) { + const dudoc = await document.getStatus(domainId, document.TYPE_DOMAIN_USER, 0, udoc._id); + if (udoc._id === 1) dudoc.role = 'guest'; + if (udoc.priv === 1) dudoc.role = 'admin'; + const p = await document.get(domainId, document.TYPE_DOMAIN_USER, dudoc.role || 'default'); + dudoc.perm = p ? p.content : builtin.BUILTIN_ROLES[dudoc.role || 'default'].perm; + return dudoc; } async function getById(domainId, _id) { const udoc = await coll.findOne({ _id }); if (!udoc) throw new UserNotFoundError(_id); - udoc.perm = await getPerm(domainId, udoc); - return new USER(udoc); + const dudoc = await getInDomain(domainId, udoc); + return new USER(udoc, dudoc); } async function getList(domainId, uids) { @@ -73,8 +71,8 @@ async function getByUname(domainId, uname) { const unameLower = uname.trim().toLowerCase(); const udoc = await coll.findOne({ unameLower }); if (!udoc) throw new UserNotFoundError(uname); - udoc.perm = await getPerm(domainId, udoc); - return new USER(udoc); + const dudoc = await getInDomain(domainId, udoc); + return new USER(udoc, dudoc); } async function getByEmail(domainId, mail, ignoreMissing = false) { @@ -84,8 +82,8 @@ async function getByEmail(domainId, mail, ignoreMissing = false) { if (ignoreMissing) return null; throw new UserNotFoundError(mail); } - udoc.perm = await getPerm(domainId, udoc); - return new USER(udoc); + const dudoc = await getInDomain(domainId, udoc); + return new USER(udoc, dudoc); } function setPassword(uid, password) { @@ -152,6 +150,10 @@ function getMulti(params) { return coll.find(params); } +function setInDomain(domainId, uid, params) { + return document.setStatus(domainId, document.TYPE_DOMAIN_USER, 0, uid, params); +} + async function getPrefixList(prefix, limit = 50) { prefix = prefix.toLowerCase(); const $regex = new RegExp(`\\A\\Q${prefix.replace(/\\E/gmi, /\\E\\E\\Q/gmi)}\\E`, 'gmi'); @@ -160,21 +162,21 @@ async function getPrefixList(prefix, limit = 50) { } function setRole(domainId, uid, role) { - return document.setStatus(domainId, document.TYPE_DOMAIN_ROLE, 0, uid, { role }); + return document.setStatus(domainId, document.TYPE_DOMAIN_USER, 0, uid, { role }); } function setRoles(domainId, roles) { const tasks = []; for (const role in roles) { tasks.push(document.set( - domainId, document.TYPE_DOMAIN_ROLE, role, { content: roles[role] }, + domainId, document.TYPE_DOMAIN_USER, role, { content: roles[role] }, )); } return Promise.all(tasks); } async function getRoles(domainId) { - const docs = await document.getMulti(domainId, document.TYPE_DOMAIN_ROLE).sort('_id', 1).toArray(); + const docs = await document.getMulti(domainId, document.TYPE_DOMAIN_USER).sort('_id', 1).toArray(); const roles = []; for (const doc of docs) { roles.push({ _id: doc.docId, perm: doc.content }); @@ -183,21 +185,21 @@ async function getRoles(domainId) { } function getRole(domainId, name) { - return document.get(domainId, document.TYPE_DOMAIN_ROLE, name); + return document.get(domainId, document.TYPE_DOMAIN_USER, name); } -function getInDomain(domainId) { - return document.getMultiStatus(domainId, document.TYPE_DOMAIN_ROLE).toArray(); +function getMultiInDomain(domainId) { + return document.getMultiStatus(domainId, document.TYPE_DOMAIN_USER); } function addRole(domainId, name, permission) { - return document.add(domainId, permission, 1, document.TYPE_DOMAIN_ROLE, name); + return document.add(domainId, permission, 1, document.TYPE_DOMAIN_USER, name); } function deleteRoles(domainId, roles) { return Promise.all([ - document.deleteMulti(domainId, document.TYPE_DOMAIN_ROLE, { docId: { $in: roles } }), - document.deleteMultiStatus(domainId, document.TYPE_DOMAIN_ROLE, { role: { $in: roles } }), + document.deleteMulti(domainId, document.TYPE_DOMAIN_USER, { docId: { $in: roles } }), + document.deleteMultiStatus(domainId, document.TYPE_DOMAIN_USER, { role: { $in: roles } }), ]); } @@ -219,6 +221,8 @@ global.Hydro.model.user = module.exports = { setById, setEmail, setPassword, + setInDomain, + getMultiInDomain, getPrefixList, setRole, setRoles, diff --git a/hydro/script/rating.js b/hydro/script/rating.js index 37ed8de9e..a65770a15 100644 --- a/hydro/script/rating.js +++ b/hydro/script/rating.js @@ -14,9 +14,8 @@ async function run({ domainId, contestId }) { for (const result of rankedTsdocs) { users.push({ ...result[1], rank: result[0], old: udict[result[1].uid].rating || 1500 }); } - console.log(users); const rated = rating(users); - console.log(rated); + return rated; } global.Hydro.script.rating = module.exports = { run }; diff --git a/hydro/script/recalcRating.js b/hydro/script/recalcRating.js new file mode 100644 index 000000000..854d99d5d --- /dev/null +++ b/hydro/script/recalcRating.js @@ -0,0 +1,29 @@ +/* eslint-disable no-await-in-loop */ +const contest = require('../model/contest'); +const user = require('../model/user'); +const rating = require('../lib/rating'); + +async function run({ domainId }, report) { + const contests = await contest.getMulti(domainId).sort('endAt', -1).toArray(); + await report({ total: contests.length }); + for (const i in contests) { + const tdoc = contests[i]; + const tsdocs = await contest.getMultiStatus(domainId, { docId: tdoc.docId }) + .sort(contest.RULES[tdoc.rule].statusSort).toArray(); + const udict = await user.getList(domainId, tsdocs.map((tsdoc) => tsdoc.uid)); + const rankedTsdocs = contest.RULES[tdoc.rule].rank(tsdocs); + const users = []; + for (const result of rankedTsdocs) { + users.push({ ...result[1], rank: result[0], old: udict[result[1].uid].rating || 1500 }); + } + const rated = rating(users); + const tasks = []; + for (const udoc of rated) { + tasks.push(user.setInDomain(domainId, udoc.uid, { rating: udoc.new })); + } + await Promise.all(tasks); + await report({ current: i + 1 }); + } +} + +global.Hydro.script.recalcRating = module.exports = { run }; diff --git a/templates/domain_rank.html b/templates/domain_rank.html new file mode 100644 index 000000000..2b5d18243 --- /dev/null +++ b/templates/domain_rank.html @@ -0,0 +1,43 @@ +{% extends "layout/basic.html" %} +{% import "components/paginator.html" as paginator with context %} +{% import "components/nothing.html" as nothing with context %} +{% block content %} +
+
+
+
+
+ {% if not udocs %} + {{ nothing.render('Sorry, there is no user in this domain.') }} + {% else %} + + + + + + + + + + + + + + + {% for udoc in udocs %} + + + + + + {% endfor %} + +
{{ _('Rank') }}{{ _('Username') }}{{ _('Rating') }}
{{ (page - 1) * 100 + loop.index }}{{ user.render_inline(udoc, badge=false) }}{{ udoc.rating|default(1500)|round(0) }}
+ {{ paginator.render(page, upcount) }} + {% endif %} +
+
+
+
+
+{% endblock %} \ No newline at end of file