diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 7b3ee66d5..0b67d8b8c 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.28.40", + "version": "2.28.41", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/entry/cli.ts b/packages/hydrooj/src/entry/cli.ts index c856043bd..21a0761fb 100644 --- a/packages/hydrooj/src/entry/cli.ts +++ b/packages/hydrooj/src/entry/cli.ts @@ -33,6 +33,7 @@ async function cli() { const [, modelName, func, ...args] = argv.args as [string, string, string, ...any[]]; if (modelName === 'script') { let arg: any; + console.log(args.join(' ')); try { arg = JSON.parse(args.join(' ')); } catch (e) { diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 63cbcaed3..61039d424 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -341,6 +341,11 @@ export interface DomainDoc extends Record { host?: string[], } +export interface DomainUnion { + _id: string; + union: string[]; +} + // Message export interface MessageDoc { _id: ObjectID, @@ -481,7 +486,7 @@ export interface Task { } export interface UploadStream extends Writable { - id: ObjectID + id: ObjectID; } export interface HydroFileSystem { @@ -516,27 +521,28 @@ export interface FileNode { } export interface Collections { - 'blacklist': BlacklistDoc, - 'contest': Tdoc, - 'domain': DomainDoc, - 'domain.user': any, - 'record': RecordDoc, - 'document': any, - 'problem': ProblemDoc, - 'user': Udoc, - 'check': any, - 'message': MessageDoc, - 'token': TokenDoc, - 'status': any, - 'oauth': any, - 'system': System, - 'task': Task, - 'storage': FileNode, - 'oplog': OplogDoc, - 'opcount': any, - 'fs.chunks': any, - 'fs.files': any, - 'document.status': any, + 'blacklist': BlacklistDoc; + 'contest': Tdoc; + 'domain': DomainDoc; + 'domain.user': any; + 'domain.union': DomainUnion; + 'record': RecordDoc; + 'document': any; + 'problem': ProblemDoc; + 'user': Udoc; + 'check': any; + 'message': MessageDoc; + 'token': TokenDoc; + 'status': any; + 'oauth': any; + 'system': System; + 'task': Task; + 'storage': FileNode; + 'oplog': OplogDoc; + 'opcount': any; + 'fs.chunks': any; + 'fs.files': any; + 'document.status': any; } export interface Model { diff --git a/packages/hydrooj/src/model/domain.ts b/packages/hydrooj/src/model/domain.ts index 113672102..e656eebe1 100644 --- a/packages/hydrooj/src/model/domain.ts +++ b/packages/hydrooj/src/model/domain.ts @@ -10,6 +10,7 @@ import UserModel, { deleteUserCache } from './user'; const coll = db.collection('domain'); const collUser = db.collection('domain.user'); +const collUnion = db.collection('domain.union'); interface DomainUserArg { _id: number, @@ -256,6 +257,21 @@ class DomainModel { await collUser.deleteMany({ domainId }); await bus.parallel('domain/delete', domainId); } + + @ArgMethod + static async addUnion(domainId: string, union: string[]) { + return await collUnion.updateOne({ _id: domainId }, { $set: { union } }, { upsert: true }); + } + + @ArgMethod + static async removeUnion(domainId: string) { + return await collUnion.deleteOne({ _id: domainId }); + } + + @ArgMethod + static async getUnion(domainId: string) { + return await collUnion.findOne({ _id: domainId }); + } } export default DomainModel; diff --git a/packages/hydrooj/src/script/rating.ts b/packages/hydrooj/src/script/rating.ts index 0ab565b91..5cec0950b 100644 --- a/packages/hydrooj/src/script/rating.ts +++ b/packages/hydrooj/src/script/rating.ts @@ -1,7 +1,8 @@ /* eslint-disable no-await-in-loop */ -import { NumericDictionary } from 'lodash'; +import { unionWith, NumericDictionary } from 'lodash'; import { ObjectID, FilterQuery } from 'mongodb'; import { Tdoc, ProblemDoc, Udoc } from '../interface'; +import db from '../service/db'; import domain from '../model/domain'; import * as contest from '../model/contest'; import problem from '../model/problem'; @@ -39,7 +40,7 @@ async function runProblem(...arg: any[]) { ), page, 100, - ); + ) as any[][]; const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true); for (const psdoc of psdocs) { if (rdict[psdoc.rid.toHexString()]) { @@ -61,7 +62,7 @@ async function runContest(...arg: any[]) { : arg[0]; const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1]; const report = (typeof arg[0] === 'string') ? arg[3] : arg[2]; - const cursor = await contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType) + const cursor = contest.getMultiStatus(tdoc.domainId, tdoc.docId, tdoc.docType) .sort(contest.RULES[tdoc.rule].statusSort); if (!await cursor.count()) return; const rankedTsdocs = await contest.RULES[tdoc.rule].ranked(tdoc, cursor); @@ -90,7 +91,7 @@ export async function calcLevel(domainId: string, report: Function) { let last = { rp: null }; let rank = 0; let count = 0; - const coll = global.Hydro.service.db.collection('domain.user'); + const coll = db.collection('domain.user'); const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 }); let bulk = coll.initializeUnorderedBulkOp(); while (await ducur.hasNext()) { @@ -111,22 +112,26 @@ export async function calcLevel(domainId: string, report: Function) { domainId, $and: [{ rank: { $lte: (levels[i] * count) / 100 } }], }; - if (i < levels.length) query.$and.push({ rank: { $gt: (levels[i + 1] * count) / 100 } }); + if (i < levels.length - 1) query.$and.push({ rank: { $gt: (levels[i + 1] * count) / 100 } }); bulk.find(query).update({ $set: { level: i } }); } await bulk.execute(); } -async function runInDomain(domainId: string, isSub: boolean, report: Function) { +async function runInDomain(id: string, isSub: boolean, report: Function) { + const info = await domain.getUnion(id); + info.union.push(id); const udict: ND = {}; const deltaudict: ND = {}; - const dudocs = await domain.getMultiUserInDomain(domainId).toArray(); - for (const dudoc of dudocs) { - deltaudict[dudoc.uid] = dudoc.rpdelta || 0; - } - const contests: Tdoc<30 | 60>[] = await contest.getMulti(domainId, { rated: true }) + const domainId = info ? { $in: info.union } : { $exists: false }; + const dudocs = unionWith([ + ...await domain.getMultiUserInDomain(id).toArray(), + ...await domain.getMultiUserInDomain('', { domainId }).toArray(), + ], (a, b) => a.uid === b.uid); + for (const dudoc of dudocs) deltaudict[dudoc.uid] = dudoc.rpdelta; + const contests: Tdoc<30 | 60>[] = await contest.getMulti('', { domainId, rated: true }) .sort('endAt', -1).toArray() as any; - await report({ message: `Found ${contests.length} contests in ${domainId}` }); + await report({ message: `Found ${contests.length} contests in ${id}` }); for (const i in contests) { const tdoc = contests[i]; await runContest(tdoc, udict, report); @@ -137,8 +142,8 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) { } } // TODO pagination - const problems = await problem.getMulti(domainId, { hidden: false }).toArray(); - await report({ message: `Found ${problems.length} problems in ${domainId}` }); + const problems = await problem.getMulti('', { domainId, hidden: false }).toArray(); + await report({ message: `Found ${problems.length} problems in ${id}` }); for (const i in problems) { const pdoc = problems[i]; await runProblem(pdoc, udict); @@ -148,16 +153,16 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) { }); } } - await domain.setMultiUserInDomain(domainId, {}, { rp: 1500 }); + await domain.setMultiUserInDomain(id, {}, { rp: 1500 }); const tasks = []; for (const uid in udict) { const rp = udict[uid] + (deltaudict[uid] || 0); - tasks.push( - domain.updateUserInDomain(domainId, +uid, { $set: { rp }, $push: { ratingHistory: rp } }), - ); + const update: any = { $set: { rp } }; + if (isSub) update.$push = { ratingHistory: rp }; + tasks.push(domain.updateUserInDomain(id, +uid, update)); } await Promise.all(tasks); - await calcLevel(domainId, report); + await calcLevel(id, report); } export async function run({ domainId }, report: Function) {