文件结构更改

This commit is contained in:
undefined committed 2020-08-04 13:42:42 +08:00
1 parent b3af28dc43
commit 1f1d12ad73
109 files changed
+544 -3296

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
/* eslint-disable no-use-before-define */
import { ObjectID } from 'mongodb';
import * as document from '../model/document';
import * as discussion from '../model/discussion';
import * as user from '../model/user';
import * as blacklist from '../model/blacklist';
import * as db from '../service/db';
export const description = 'Add blacklist by ip, uid';
async function _address(
ip: string,
bset: Set<string>, uset: Set<number>, dset: Set<ObjectID>,
dryrun: boolean, report: Function,
) {
if (bset.has(ip)) return;
bset.add(ip);
report({ message: `ip ${ip}` });
const users = await db.collection('user').find({ loginip: ip }).toArray();
const tasks = [];
for (const udoc of users) {
tasks.push(_user(udoc._id, bset, uset, dset, dryrun, report));
}
await Promise.all(tasks);
if (!dryrun) await blacklist.add(ip);
}
async function _discussion(
domainId: string, did: ObjectID,
bset: Set<string>, uset: Set<number>, dset: Set<ObjectID>,
dryrun: boolean, report: Function,
) {
if (dset.has(did)) return;
dset.add(did);
const ddoc = await discussion.get(domainId, did);
if (!ddoc) return;
report({ message: `discussion ${ddoc.title}` });
await _user(ddoc.owner, bset, uset, dset, dryrun, report);
if (ddoc.ip) await _address(ddoc.ip, bset, uset, dset, dryrun, report);
if (!dryrun) await discussion.del(domainId, ddoc.docId);
}
async function _user(
uid: number,
bset: Set<string>, uset: Set<number>, dset: Set<ObjectID>,
dryrun: boolean, report: Function,
) {
if (uset.has(uid)) return;
uset.add(uid);
const udoc = await user.getById('system', uid);
if (!udoc) return;
report({ message: `user ${udoc._id} ${udoc.uname}` });
await _address(udoc.loginip(), bset, uset, dset, dryrun, report);
const ddocs = await db.collection('document').find({ docType: document.TYPE_DISCUSSION })
.sort({ domainId: 1, docId: 1 }).toArray();
const tasks = [];
for (const ddoc of ddocs) {
tasks.push(_discussion(ddoc.domainId, ddoc.docId, bset, uset, dset, dryrun, report));
}
await Promise.all(tasks);
if (!dryrun) await user.ban(uid);
}
export async function run({
// eslint-disable-next-line no-shadow
address = null, discuss = null, user = null, dryrun = true,
}, report) {
if (address) await _address(address, new Set(), new Set(), new Set(), dryrun, report);
if (discuss) {
await _discussion(
discuss.domainId, new ObjectID(discuss.did),
new Set(), new Set(), new Set(), dryrun, report,
);
}
if (user) await _user(user, new Set(), new Set(), new Set(), dryrun, report);
}
export const validate = {
$or: {
address: 'string',
discuss: ObjectID,
user: 'number',
},
dryrun: 'boolean',
};
global.Hydro.script.blacklist = { run, description, validate };
+94
View File
@@ -0,0 +1,94 @@
/* eslint-disable no-await-in-loop */
import * as user from '../model/user';
import * as document from '../model/document';
import * as setting from '../model/setting';
import * as db from '../service/db';
import paginate from '../lib/paginate';
import { STATUS, PRIV } from '../model/builtin';
export const description = 'Delete a user';
const collDocument = db.collection('document');
const collDomainUser = db.collection('domain.user');
const collRecord = db.collection('record');
const $unset = {
regip: '', regat: '', loginat: '', loginip: '',
};
for (const s of setting.PREFERENCE_SETTINGS) {
$unset[s.key] = '';
}
async function deleteRecord(uid: number) {
const [, pcount] = await paginate(
collRecord.find(),
1,
100,
);
for (let i = 1; i <= pcount; i++) {
const [rdocs] = await paginate(
collRecord.find(),
1,
100,
);
const tasks = [];
for (const rdoc of rdocs) {
if (rdoc.pid) {
tasks.push(document.inc(rdoc.domainId, document.TYPE_PROBLEM, rdoc.pid, 'nSubmit', -1));
}
if (rdoc.status === STATUS.STATUS_ACCEPTED) {
tasks.push(document.inc(rdoc.domainId, document.TYPE_PROBLEM, rdoc.pid, 'nAccept', -1));
}
}
await Promise.all(tasks);
}
await collRecord.deleteMany({ uid });
}
async function deleteDiscussion(uid: number) {
const [, pcount] = await paginate(
collDocument.find({ docType: document.TYPE_DISCUSSION, owner: uid }),
1,
100,
);
for (let i = 1; i <= pcount; i++) {
const [ddocs] = await paginate(
collDocument.find({ docType: document.TYPE_DISCUSSION, owner: uid }),
1,
100,
);
const tasks = [];
for (const ddoc of ddocs) {
tasks.push(document.deleteMulti(ddoc.domainId, document.TYPE_DISCUSSION_REPLY, {
parentId: ddoc.docId,
parentType: document.TYPE_DISCUSSION,
}));
}
await Promise.all(tasks);
}
await collDocument.deleteMany({ docType: document.TYPE_DISCUSSION, owner: uid });
// TODO delete replies
}
export async function run({
uid,
}) {
await deleteRecord(uid);
await deleteDiscussion(uid);
await collDomainUser.deleteMany({ uid });
const uname = String.random(32);
await user.setById(uid, {
uname,
unameLower: uname.toLowerCase(),
priv: PRIV.PRIV_NONE,
}, $unset);
await user.setPassword(uid, String.random(32));
// TODO delete contest, homework, etc
return uid;
}
export const validate = {
uid: 'number',
};
global.Hydro.script.deleteUser = { run, description, validate };
@@ -0,0 +1,49 @@
import * as db from '../service/db';
import { STATUS } from '../model/builtin';
import * as document from '../model/document';
export const description = 'Recalcuates nSubmit and nAccept in problem status.';
export async function run() {
const pipeline = [
{
$match: { hidden: false, type: { $ne: 'run' } },
},
{
$group: {
_id: { domainId: '$domainId', pid: '$pid', uid: '$uid' },
nSubmit: { $sum: 1 },
nAccept: {
$sum: {
$cond: [{ $eq: ['$status', STATUS.STATUS_ACCEPTED] }, 1, 0],
},
},
},
},
{
$group: {
_id: { domainId: '$_id.domainId', pid: '$_id.pid' },
nSubmit: { $sum: '$nSubmit' },
nAccept: { $sum: { $min: ['$nAccept', 1] } },
},
},
];
const bulk = db.collection('document').initializeUnorderedBulkOp();
db.collection('record').aggregate(pipeline).each(
(err, adoc: any) => bulk.find({
domainId: adoc._id.domainId,
docType: document.TYPE_PROBLEM,
docId: adoc._id.pid,
}).updateOne({
$set: {
nSubmit: adoc.nSubmit,
nAccept: adoc.nAccept,
},
}),
);
await bulk.execute();
}
export const validate = {};
global.Hydro.script.problemStat = { run, description, validate };
+148
View File
@@ -0,0 +1,148 @@
/* eslint-disable no-await-in-loop */
import { NumericDictionary } from 'lodash';
import { ObjectID } from 'mongodb';
import { Tdoc, Pdoc } from '../interface';
import * as domain from '../model/domain';
import * as contest from '../model/contest';
import * as problem from '../model/problem';
import * as record from '../model/record';
import { STATUS } from '../model/builtin';
import rating from '../lib/rating';
import paginate from '../lib/paginate';
export const description = 'Calculate rp of a domain, or all domains';
type ND = NumericDictionary<number>
async function runProblem(pdoc: Pdoc, udict: ND): Promise<void>
async function runProblem(domainId: string, pid: number, udict: ND): Promise<void>
async function runProblem(...arg: any[]) {
const pdoc: Pdoc = (typeof arg[0] === 'string')
? await problem.get(arg[0], arg[1], -1)
: arg[0];
const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1];
const [, nPages] = await paginate(
problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, rid: { $ne: null } },
),
1,
100,
);
const nAccept = await problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, status: STATUS.STATUS_ACCEPTED },
).count();
const p = (pdoc.difficulty || 5) / (Math.sqrt(Math.sqrt(nAccept)) + 1);
for (let page = 1; page <= nPages; page++) {
const [psdocs] = await paginate(
problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, rid: { $ne: null } },
),
page,
100,
);
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true);
for (const psdoc of psdocs) {
if (rdict[psdoc.rid]) {
const rp = rdict[psdoc.rid].score * p;
udict[psdoc.uid] = (udict[psdoc.uid] || 1500) + rp;
}
}
}
}
async function runContest(tdoc: Tdoc, udict: ND, report: Function): Promise<void>
async function runContest(
domainId: string, tid: ObjectID, udict: ND, report: Function
): Promise<void>
async function runContest(...arg: any[]) {
const start = new Date().getTime();
const tdoc: Tdoc = (typeof arg[0] === 'string')
? await contest.get(arg[0], arg[1], -1)
: arg[0];
const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1];
const report = (typeof arg[0] === 'string') ? arg[3] : arg[2];
const tsdocs = await contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType)
.sort(contest.RULES[tdoc.rule].statusSort).toArray();
if (!tsdocs.length) return;
const rankedTsdocs = contest.RULES[tdoc.rule].rank(tsdocs);
const users = [];
for (const result of rankedTsdocs) {
users.push({ uid: result[1].uid, rank: result[0], old: udict[result[1].uid] || 1500 });
}
// FIXME sum(rating.new) always less than sum(rating.old)
const rated = rating(users);
for (const udoc of rated) udict[udoc.uid] = udoc.new;
await report({
case: {
status: STATUS.STATUS_ACCEPTED,
message: `Contest ${tdoc.title} finished`,
time: new Date().getTime() - start,
memory: 0,
score: 0,
},
});
}
async function runInDomain(domainId: string, isSub: boolean, report: Function) {
const udict: ND = {};
const contests = await contest.getMulti(domainId, { rated: true }, -1).sort('endAt', -1).toArray();
await report({ message: `Found ${contests.length} contests in ${domainId}` });
for (const i in contests) {
const tdoc = contests[i];
await runContest(tdoc, udict, report);
if (!isSub) {
await report({
progress: Math.floor(((parseInt(i, 10) + 1) / contests.length) * 100),
});
}
}
// TODO pagination
const problems = await problem.getMulti(domainId, { hidden: false }).toArray();
await report({ message: `Found ${problems.length} problems in ${domainId}` });
for (const i in problems) {
const pdoc = problems[i];
await runProblem(pdoc, udict);
if (!isSub) {
await report({
progress: Math.floor(((parseInt(i, 10) + 1) / problems.length) * 100),
});
}
}
await domain.setMultiUserInDomain(domainId, {}, { rp: 1500 });
const tasks = [];
for (const uid in udict) {
tasks.push(domain.setUserInDomain(domainId, parseInt(uid, 10), { rp: udict[uid] }));
}
await Promise.all(tasks);
}
export async function run({ domainId }, report: Function) {
if (!domainId) {
const domains = await domain.getMulti().toArray();
await report({ message: `Found ${domains.length} domains` });
for (const i in domains) {
const start = new Date().getTime();
await runInDomain(domains[i]._id, true, report);
await report({
case: {
status: STATUS.STATUS_ACCEPTED,
message: `Domain ${domains[i]._id} finished`,
time: new Date().getTime() - start,
memory: 0,
score: 0,
},
progress: Math.floor(((parseInt(i, 10) + 1) / domains.length) * 100),
});
}
} else await runInDomain(domainId, false, report);
return true;
}
export const validate = {
$or: [
{ domainId: 'string' },
{ domainId: 'undefined' },
],
};
global.Hydro.script.rp = { run, description, validate };
+20
View File
@@ -0,0 +1,20 @@
import * as user from '../model/user';
export const description = 'Create a new user';
export async function run({
uname, password, mail, uid,
}) {
if (uid) uid = parseInt(uid, 10);
if (uid && Number.isNaN(uid)) throw new Error('uid');
uid = await user.create(mail, uname, password, uid);
return uid;
}
export const validate = {
uname: { $type: 'string' },
password: { $type: 'string' },
mail: { $type: 'string' },
};
global.Hydro.script.register = { run, description, validate };
@@ -0,0 +1,18 @@
// TODO add a set_priv interface
import * as user from '../model/user';
import { PRIV } from '../model/builtin';
export const description = 'Set a user as superadmin.';
export async function run({ uid }) {
uid = parseInt(uid, 10);
if (Number.isNaN(uid)) throw new Error('uid');
await user.setPriv(uid, PRIV.PRIV_ALL);
return uid;
}
export const validate = {
uid: { $type: 'number' },
};
global.Hydro.script.setSuperadmin = { run, description, validate };
+13
View File
@@ -0,0 +1,13 @@
import * as domain from '../model/domain';
export const description = 'Upgrade database from 0 to 1';
export async function run() {
const ddoc = await domain.get('system');
if (!ddoc) await domain.add('system', 0, 'Hydro', 'Hydro System');
// TODO discussion node?
}
export const validate = {};
global.Hydro.script.upgrade0_1 = { run, description, validate };