/ranking & script.recalcRanking

This commit is contained in:
masnn committed 2020-06-07 19:11:19 +08:00
1 parent 4aa1c9fe1f
commit 7a4d382dc9
9 files changed
+154 -53

No files matched your search

+26
View File
@@ -1,10 +1,35 @@
const user = require('../model/user'); const user = require('../model/user');
const domain = require('../model/domain'); const domain = require('../model/domain');
const system = require('../model/system'); const system = require('../model/system');
const paginate = require('../lib/paginate');
const { Route, Handler } = require('../service/server'); const { Route, Handler } = require('../service/server');
const { PERM_MANAGE } = require('../permission'); const { PERM_MANAGE } = require('../permission');
const { RoleAlreadyExistError, ValidationError, PermissionError } = require('../error'); 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 { class DomainCreateHandler extends Handler {
async prepare() { async prepare() {
if (this.user.priv !== 1) { if (this.user.priv !== 1) {
@@ -158,6 +183,7 @@ class DomainRoleHandler extends ManageHandler {
} }
async function apply() { async function apply() {
Route('/ranking', DomainRankHandler);
Route('/domain/create', DomainCreateHandler); Route('/domain/create', DomainCreateHandler);
Route('/domain/dashboard', DomainDashboardHandler); Route('/domain/dashboard', DomainDashboardHandler);
Route('/domain/edit', DomainEditHandler); Route('/domain/edit', DomainEditHandler);
+2 -2
View File
@@ -1,9 +1,9 @@
class User { class User {
constructor(rank, old, handle = 0) { constructor(rank, old, uid = 0) {
this.rank = rank; this.rank = rank;
this.old = old; this.old = old;
this.seed = 1; this.seed = 1;
this.handle = handle.toString(); this.uid = uid;
} }
} }
+1 -1
View File
@@ -272,7 +272,7 @@ async function load() {
} }
} }
const builtinScript = [ const builtinScript = [
'install', 'uninstall', 'rating', 'install', 'uninstall', 'rating', 'recalcRating',
]; ];
for (const i of builtinScript) require(`./script/${i}`); for (const i of builtinScript) require(`./script/${i}`);
await script(); await script();
+2 -2
View File
@@ -5,7 +5,7 @@ const db = require('../service/db');
const coll = db.collection('document'); const coll = db.collection('document');
const collStatus = db.collection('document.status'); const collStatus = db.collection('document.status');
const TYPE_DOMAIN_ROLE = 0; const TYPE_DOMAIN_USER = 0;
const TYPE_PROBLEM = 10; const TYPE_PROBLEM = 10;
const TYPE_PROBLEM_SOLUTION = 11; const TYPE_PROBLEM_SOLUTION = 11;
const TYPE_PROBLEM_LIST = 12; const TYPE_PROBLEM_LIST = 12;
@@ -353,7 +353,7 @@ global.Hydro.model.document = module.exports = {
setStatus, setStatus,
setSub, setSub,
TYPE_DOMAIN_ROLE, TYPE_DOMAIN_USER,
TYPE_CONTEST, TYPE_CONTEST,
TYPE_DISCUSSION, TYPE_DISCUSSION,
TYPE_DISCUSSION_NODE, TYPE_DISCUSSION_NODE,
+1 -1
View File
@@ -25,7 +25,7 @@ function add(domainId, owner, name) {
tasks.push( tasks.push(
document.add( document.add(
domainId, builtin.BUILTIN_ROLES[id].perm, owner, domainId, builtin.BUILTIN_ROLES[id].perm, owner,
document.TYPE_DOMAIN_ROLE, id, document.TYPE_DOMAIN_USER, id,
), ),
); );
} }
+49 -45
View File
@@ -9,24 +9,27 @@ const db = require('../service/db');
const coll = db.collection('user'); const coll = db.collection('user');
class USER { class USER {
constructor(user) { constructor(udoc, dudoc) {
this._id = user._id; this.udoc = () => udoc;
this.mail = user.mail; this.dudoc = () => dudoc;
this.uname = user.uname; this._id = udoc._id;
this.salt = () => user.salt; this.mail = udoc.mail;
this.hash = () => user.hash; this.uname = udoc.uname;
this.perm = user.perm; this.salt = () => udoc.salt;
this.priv = user.priv; this.hash = () => udoc.hash;
this.role = user.role; this.priv = udoc.priv;
this.viewLang = user.language || 'zh_CN'; this.viewLang = udoc.language || 'zh_CN';
this.codeLang = user.codeLang || 'c'; this.codeLang = udoc.codeLang || 'c';
this.codeTemplate = user.codeTemplate || ''; this.codeTemplate = udoc.codeTemplate || '';
this.regat = user.regat; this.regat = udoc.regat;
this.loginat = user.loginat; this.loginat = udoc.loginat;
this.bio = user.bio || ''; this.bio = udoc.bio || '';
this.gravatar = user.gravatar || ''; this.gravatar = udoc.gravatar || '';
this.nAccept = user.nAccept || 0; this.nAccept = dudoc.nAccept || 0;
this.nSubmit = user.nSubmit || 0; this.nSubmit = dudoc.nSubmit || 0;
this.rating = dudoc.rating || 1500;
this.perm = dudoc.perm;
this.role = dudoc.role;
} }
hasPerm(p) { hasPerm(p) {
@@ -40,25 +43,20 @@ class USER {
} }
} }
async function getPerm(domainId, udoc) { async function getInDomain(domainId, udoc) {
if (udoc._id === 1) { // Is Guest const dudoc = await document.getStatus(domainId, document.TYPE_DOMAIN_USER, 0, udoc._id);
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'guest'); if (udoc._id === 1) dudoc.role = 'guest';
return p ? p.content : builtin.BUILTIN_ROLES.guest.perm; if (udoc.priv === 1) dudoc.role = 'admin';
} const p = await document.get(domainId, document.TYPE_DOMAIN_USER, dudoc.role || 'default');
if (udoc.priv === 1) { // Is superadmin dudoc.perm = p ? p.content : builtin.BUILTIN_ROLES[dudoc.role || 'default'].perm;
const p = await document.get(domainId, document.TYPE_DOMAIN_ROLE, 'admin'); return dudoc;
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 getById(domainId, _id) { async function getById(domainId, _id) {
const udoc = await coll.findOne({ _id }); const udoc = await coll.findOne({ _id });
if (!udoc) throw new UserNotFoundError(_id); if (!udoc) throw new UserNotFoundError(_id);
udoc.perm = await getPerm(domainId, udoc); const dudoc = await getInDomain(domainId, udoc);
return new USER(udoc); return new USER(udoc, dudoc);
} }
async function getList(domainId, uids) { async function getList(domainId, uids) {
@@ -73,8 +71,8 @@ async function getByUname(domainId, uname) {
const unameLower = uname.trim().toLowerCase(); const unameLower = uname.trim().toLowerCase();
const udoc = await coll.findOne({ unameLower }); const udoc = await coll.findOne({ unameLower });
if (!udoc) throw new UserNotFoundError(uname); if (!udoc) throw new UserNotFoundError(uname);
udoc.perm = await getPerm(domainId, udoc); const dudoc = await getInDomain(domainId, udoc);
return new USER(udoc); return new USER(udoc, dudoc);
} }
async function getByEmail(domainId, mail, ignoreMissing = false) { async function getByEmail(domainId, mail, ignoreMissing = false) {
@@ -84,8 +82,8 @@ async function getByEmail(domainId, mail, ignoreMissing = false) {
if (ignoreMissing) return null; if (ignoreMissing) return null;
throw new UserNotFoundError(mail); throw new UserNotFoundError(mail);
} }
udoc.perm = await getPerm(domainId, udoc); const dudoc = await getInDomain(domainId, udoc);
return new USER(udoc); return new USER(udoc, dudoc);
} }
function setPassword(uid, password) { function setPassword(uid, password) {
@@ -152,6 +150,10 @@ function getMulti(params) {
return coll.find(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) { async function getPrefixList(prefix, limit = 50) {
prefix = prefix.toLowerCase(); prefix = prefix.toLowerCase();
const $regex = new RegExp(`\\A\\Q${prefix.replace(/\\E/gmi, /\\E\\E\\Q/gmi)}\\E`, 'gmi'); 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) { 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) { function setRoles(domainId, roles) {
const tasks = []; const tasks = [];
for (const role in roles) { for (const role in roles) {
tasks.push(document.set( 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); return Promise.all(tasks);
} }
async function getRoles(domainId) { 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 = []; const roles = [];
for (const doc of docs) { for (const doc of docs) {
roles.push({ _id: doc.docId, perm: doc.content }); roles.push({ _id: doc.docId, perm: doc.content });
@@ -183,21 +185,21 @@ async function getRoles(domainId) {
} }
function getRole(domainId, name) { 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) { function getMultiInDomain(domainId) {
return document.getMultiStatus(domainId, document.TYPE_DOMAIN_ROLE).toArray(); return document.getMultiStatus(domainId, document.TYPE_DOMAIN_USER);
} }
function addRole(domainId, name, permission) { 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) { function deleteRoles(domainId, roles) {
return Promise.all([ return Promise.all([
document.deleteMulti(domainId, document.TYPE_DOMAIN_ROLE, { docId: { $in: roles } }), document.deleteMulti(domainId, document.TYPE_DOMAIN_USER, { docId: { $in: roles } }),
document.deleteMultiStatus(domainId, document.TYPE_DOMAIN_ROLE, { role: { $in: roles } }), document.deleteMultiStatus(domainId, document.TYPE_DOMAIN_USER, { role: { $in: roles } }),
]); ]);
} }
@@ -219,6 +221,8 @@ global.Hydro.model.user = module.exports = {
setById, setById,
setEmail, setEmail,
setPassword, setPassword,
setInDomain,
getMultiInDomain,
getPrefixList, getPrefixList,
setRole, setRole,
setRoles, setRoles,
+1 -2
View File
@@ -14,9 +14,8 @@ async function run({ domainId, contestId }) {
for (const result of rankedTsdocs) { for (const result of rankedTsdocs) {
users.push({ ...result[1], rank: result[0], old: udict[result[1].uid].rating || 1500 }); users.push({ ...result[1], rank: result[0], old: udict[result[1].uid].rating || 1500 });
} }
console.log(users);
const rated = rating(users); const rated = rating(users);
console.log(rated); return rated;
} }
global.Hydro.script.rating = module.exports = { run }; global.Hydro.script.rating = module.exports = { run };
+29
View File
@@ -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 };
+43
View File
@@ -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 %}
<div class="row">
<div class="medium-12 columns">
<div class="section">
<div class="section__body no-padding">
<div data-fragment-id="ranking">
{% if not udocs %}
{{ nothing.render('Sorry, there is no user in this domain.') }}
{% else %}
<table class="data-table">
<colgroup>
<col class="col--rank">
<col class="col--user">
<col class="col--rating">
</colgroup>
<thead>
<tr>
<th class="col--rank">{{ _('Rank') }}</th>
<th class="col--user">{{ _('Username') }}</th>
<th class="col--rating">{{ _('Rating') }}</th>
</tr>
</thead>
<tbody>
{% for udoc in udocs %}
<tr>
<td class="col--rank">{{ (page - 1) * 100 + loop.index }}</td>
<td class="col--user">{{ user.render_inline(udoc, badge=false) }}</td>
<td class="col--rating">{{ udoc.rating|default(1500)|round(0) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ paginator.render(page, upcount) }}
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}