mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
Merge branch 'master' into contest-access-lock
This commit is contained in:
commit
73dc91c342
146 files changed
+751
-757
No files matched your search
@@ -2,7 +2,7 @@ import AdmZip from 'adm-zip';
|
||||
import { readFile, statSync } from 'fs-extra';
|
||||
import { isBinaryFile } from 'isbinaryfile';
|
||||
import {
|
||||
escapeRegExp, flattenDeep, intersection, isSafeInteger, pick,
|
||||
escapeRegExp, flattenDeep, intersection, isSafeInteger, pick, uniqBy,
|
||||
} from 'lodash';
|
||||
import { FilterQuery, ObjectID } from 'mongodb';
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -35,6 +35,7 @@ import * as bus from '../service/bus';
|
||||
import {
|
||||
Handler, param, post, query, route, Types,
|
||||
} from '../service/server';
|
||||
import { buildProjection } from '../utils';
|
||||
import { registerResolver, registerValue } from './api';
|
||||
import { ContestDetailBaseHandler } from './contest';
|
||||
|
||||
@@ -992,18 +993,22 @@ export class ProblemCreateHandler extends Handler {
|
||||
export class ProblemPrefixListHandler extends Handler {
|
||||
@param('prefix', Types.Name)
|
||||
async get(domainId: string, prefix: string) {
|
||||
const pdocs = await problem.getPrefixList(domainId, prefix);
|
||||
if (!Number.isNaN(+prefix) && !pdocs.filter((i) => i.docId === +prefix)) {
|
||||
const pdoc = await problem.get(domainId, +prefix, ['domainId', 'docId', 'pid', 'title']);
|
||||
if (pdoc) pdocs.unshift(pdoc);
|
||||
}
|
||||
const projection = ['domainId', 'docId', 'pid', 'title'] as const;
|
||||
const [pdocs, pdoc, apdoc] = await Promise.all([
|
||||
problem.getPrefixList(domainId, prefix),
|
||||
problem.get(domainId, Number.isSafeInteger(+prefix) ? +prefix : prefix, projection),
|
||||
/^P\d+$/.test(prefix) ? problem.get(domainId, +prefix.substring(1), projection) : Promise.resolve(null),
|
||||
]);
|
||||
if (apdoc) pdocs.unshift(apdoc);
|
||||
if (pdoc) pdocs.unshift(pdoc);
|
||||
if (pdocs.length < 20) {
|
||||
const search = global.Hydro.lib.problemSearch || defaultSearch;
|
||||
const result = await search(domainId, prefix, { limit: 20 - pdocs.length });
|
||||
const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } }).toArray();
|
||||
const docs = await problem.getMulti(domainId, { docId: { $in: result.hits.map((i) => +i.split('/')[1]) } })
|
||||
.project(buildProjection(projection)).toArray();
|
||||
pdocs.push(...docs);
|
||||
}
|
||||
this.response.body = pdocs;
|
||||
this.response.body = uniqBy(pdocs, 'docId');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { sendMail } from '../lib/mail';
|
||||
import { isEmail, isPassword } from '../lib/validator';
|
||||
import BlackListModel from '../model/blacklist';
|
||||
import { PERM, PRIV, STATUS } from '../model/builtin';
|
||||
import * as ContestModel from '../model/contest';
|
||||
import domain from '../model/domain';
|
||||
import oauth from '../model/oauth';
|
||||
import * as oplog from '../model/oplog';
|
||||
@@ -356,9 +357,12 @@ class UserDetailHandler extends Handler {
|
||||
}
|
||||
}
|
||||
const tags = Object.entries(acInfo).sort((a, b) => b[1] - a[1]).slice(0, 20);
|
||||
const tsdocs = await ContestModel.getMultiStatus(domainId, { uid, attend: { $exists: true } }).project({ docId: 1 }).toArray();
|
||||
const tdocs = await ContestModel.getMulti(domainId, { docId: { $in: tsdocs.map((i) => i.docId) } })
|
||||
.project({ docId: 1, title: 1, rule: 1 }).sort({ _id: -1 }).toArray();
|
||||
this.response.template = 'user_detail.html';
|
||||
this.response.body = {
|
||||
isSelfProfile, udoc, sdoc, pdocs, tags,
|
||||
isSelfProfile, udoc, sdoc, pdocs, tags, tdocs,
|
||||
};
|
||||
if (this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_SOLUTION)) {
|
||||
const psdocs = await SolutionModel.getByUser(domainId, uid).limit(10).toArray();
|
||||
|
||||
@@ -69,6 +69,9 @@ export async function add(
|
||||
payload.domainId!, payload.content!, payload.owner!, document.TYPE_DISCUSSION,
|
||||
null, payload.parentType, payload.parentId, omit(payload, ['domainId', 'content', 'owner', 'parentType', 'parentId']),
|
||||
);
|
||||
await coll.insertOne({
|
||||
domainId, docId: res, content, uid: owner, ip, time: new Date(),
|
||||
});
|
||||
payload.docId = res;
|
||||
await bus.parallel('discussion/add', payload);
|
||||
return payload.docId;
|
||||
|
||||
@@ -330,8 +330,7 @@ export class ProblemModel {
|
||||
}
|
||||
|
||||
static async getPrefixList(domainId: string, prefix: string) {
|
||||
prefix = prefix.toLowerCase();
|
||||
const $regex = new RegExp(`\\A${escapeRegExp(prefix)}`, 'gmi');
|
||||
const $regex = new RegExp(`^${escapeRegExp(prefix.toLowerCase())}`, 'i');
|
||||
const filter = { $or: [{ pid: { $regex } }, { title: { $regex } }] };
|
||||
return await document.getMulti(domainId, document.TYPE_PROBLEM, filter, ['domainId', 'docId', 'pid', 'title'])
|
||||
.limit(20).toArray();
|
||||
|
||||
@@ -60,7 +60,7 @@ export class StorageModel {
|
||||
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path');
|
||||
if (target.length && !target.endsWith('/')) target += '/';
|
||||
const results = await StorageModel.coll.find({
|
||||
path: { $regex: new RegExp(`^${escapeRegExp(target)}${recursive ? '' : '[^/]+$'}`, 'i') },
|
||||
path: { $regex: `^${escapeRegExp(target)}${recursive ? '' : '[^/]+$'}` },
|
||||
autoDelete: null,
|
||||
}).toArray();
|
||||
return results.map((i) => ({
|
||||
|
||||
@@ -367,8 +367,7 @@ class UserModel {
|
||||
|
||||
@ArgMethod
|
||||
static async getPrefixList(domainId: string, prefix: string, limit: number = 50) {
|
||||
prefix = prefix.toLowerCase();
|
||||
const $regex = new RegExp(`\\A${escapeRegExp(prefix)}`, 'gmi');
|
||||
const $regex = `^${escapeRegExp(prefix.toLowerCase())}`;
|
||||
const udocs = await coll.find({ unameLower: { $regex } })
|
||||
.limit(limit).project({ _id: 1 }).toArray();
|
||||
const users = [];
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-constant-condition */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { ObjectID } from 'mongodb';
|
||||
import Schema from 'schemastery';
|
||||
@@ -29,10 +28,8 @@ export async function udoc(report) {
|
||||
},
|
||||
];
|
||||
let bulk = db.collection('domain.user').initializeUnorderedBulkOp();
|
||||
const cursor = db.collection('record').aggregate(pipeline, { allowDiskUse: true });
|
||||
while (true) {
|
||||
const adoc = await cursor.next() as any;
|
||||
if (!adoc) break;
|
||||
const cursor = db.collection('record').aggregate<any>(pipeline, { allowDiskUse: true });
|
||||
for await (const adoc of cursor) {
|
||||
bulk.find({
|
||||
domainId: adoc._id.domainId,
|
||||
uid: adoc._id.uid,
|
||||
@@ -61,10 +58,8 @@ export async function psdoc(report) {
|
||||
},
|
||||
},
|
||||
];
|
||||
const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true });
|
||||
while (true) {
|
||||
const adoc = await data.next() as any;
|
||||
if (!adoc) break;
|
||||
const data = db.collection('record').aggregate<any>(pipeline, { allowDiskUse: true });
|
||||
for await (const adoc of data) {
|
||||
await document.setStatus(adoc._id.domainId, document.TYPE_PROBLEM, adoc._id.pid, adoc._id.uid, { nSubmit: adoc.nSubmit });
|
||||
}
|
||||
}
|
||||
@@ -117,11 +112,9 @@ export async function pdoc(report) {
|
||||
pipeline[2].$group[`s${i}`] = { $sum: `$s${i}` };
|
||||
}
|
||||
let bulk = db.collection('document').initializeUnorderedBulkOp();
|
||||
const data = db.collection('record').aggregate(pipeline, { allowDiskUse: true });
|
||||
const data = db.collection('record').aggregate<any>(pipeline, { allowDiskUse: true });
|
||||
let cnt = 0;
|
||||
while (true) {
|
||||
const adoc = await data.next() as any;
|
||||
if (!adoc) break;
|
||||
for await (const adoc of data) {
|
||||
const $set = {
|
||||
nSubmit: adoc.nSubmit,
|
||||
nAccept: adoc.nAccept,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { NumericDictionary, unionWith } from 'lodash';
|
||||
import { FilterQuery } from 'mongodb';
|
||||
import Schema from 'schemastery';
|
||||
import { Counter } from '@hydrooj/utils';
|
||||
import { Tdoc, Udoc } from '../interface';
|
||||
import difficultyAlgorithm from '../lib/difficulty';
|
||||
import rating from '../lib/rating';
|
||||
@@ -100,21 +101,15 @@ export const RpTypes: Record<string, RpDef> = {
|
||||
global.Hydro.model.rp = RpTypes;
|
||||
|
||||
export async function calcLevel(domainId: string, report: Function) {
|
||||
const filter = { rp: { $gt: 0 } };
|
||||
const ducnt = await domain.getMultiUserInDomain(domainId, filter).count();
|
||||
await domain.setMultiUserInDomain(domainId, {}, { level: 0, rank: null });
|
||||
if (!ducnt) return;
|
||||
let last = { rp: null };
|
||||
let rank = 0;
|
||||
let count = 0;
|
||||
const coll = db.collection('domain.user');
|
||||
const filter = { rp: { $gt: 0 }, uid: { $nin: [0, 1], $gt: -1000 } };
|
||||
const ducur = domain.getMultiUserInDomain(domainId, filter).project({ rp: 1 }).sort({ rp: -1 });
|
||||
let bulk = coll.initializeUnorderedBulkOp();
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const dudoc = await ducur.next();
|
||||
if (!dudoc) break;
|
||||
if ([0, 1].includes(dudoc.uid)) continue;
|
||||
for await (const dudoc of ducur) {
|
||||
count++;
|
||||
if (!dudoc.rp) dudoc.rp = null;
|
||||
if (dudoc.rp !== last.rp) rank = count;
|
||||
@@ -122,6 +117,7 @@ export async function calcLevel(domainId: string, report: Function) {
|
||||
last = dudoc;
|
||||
if (count % 100 === 0) report({ message: `#${count}: Rank ${rank}` });
|
||||
}
|
||||
if (!count) return;
|
||||
await bulk.execute();
|
||||
const levels = global.Hydro.model.builtin.LEVELS;
|
||||
bulk = coll.initializeUnorderedBulkOp();
|
||||
@@ -136,28 +132,30 @@ export async function calcLevel(domainId: string, report: Function) {
|
||||
await bulk.execute();
|
||||
}
|
||||
|
||||
async function runInDomain(id: string, report: Function) {
|
||||
const info = await domain.get(id);
|
||||
const domainIds = [id, ...(info.union || [])];
|
||||
async function runInDomain(domainId: string, report: Function) {
|
||||
const info = await domain.get(domainId);
|
||||
const domainIds = [domainId, ...(info.union || [])];
|
||||
const results: Record<keyof typeof RpTypes, ND> = {};
|
||||
const udict = new Proxy({}, { get: (self, key) => self[key] || 0 });
|
||||
const udict = Counter();
|
||||
for (const type in RpTypes) {
|
||||
results[type] = new Proxy({}, { get: (self, key) => self[key] || RpTypes[type].base });
|
||||
await RpTypes[type].run(domainIds, results[type], report);
|
||||
const bulk = db.collection('domain.user').initializeUnorderedBulkOp();
|
||||
for (const uid in results[type]) {
|
||||
const udoc = await UserModel.getById(id, +uid);
|
||||
const udoc = await UserModel.getById(domainId, +uid);
|
||||
if (!udoc?.hasPriv(PRIV.PRIV_USER_PROFILE)) continue;
|
||||
await domain.updateUserInDomain(id, +uid, { $set: { [`rpInfo.${type}`]: results[type][uid] } });
|
||||
bulk.find({ domainId, uid: +uid }).updateOne({ $set: { [`rpInfo.${type}`]: results[type][uid] } });
|
||||
udict[+uid] += results[type][uid];
|
||||
}
|
||||
if (bulk.length) await bulk.execute();
|
||||
}
|
||||
await domain.setMultiUserInDomain(id, {}, { rp: 0 });
|
||||
await domain.setMultiUserInDomain(domainId, {}, { rp: 0 });
|
||||
const bulk = db.collection('domain.user').initializeUnorderedBulkOp();
|
||||
for (const uid in udict) {
|
||||
bulk.find({ domainId: id, uid: +uid }).upsert().update({ $set: { rp: Math.max(0, udict[uid]) } });
|
||||
bulk.find({ domainId, uid: +uid }).upsert().update({ $set: { rp: Math.max(0, udict[uid]) } });
|
||||
}
|
||||
if (bulk.length) await bulk.execute();
|
||||
await calcLevel(id, report);
|
||||
await calcLevel(domainId, report);
|
||||
}
|
||||
|
||||
export async function run({ domainId }, report: Function) {
|
||||
|
||||
@@ -17,6 +17,7 @@ interface MongoConfig {
|
||||
port?: string,
|
||||
name?: string,
|
||||
url?: string,
|
||||
uri?: string,
|
||||
prefix?: string,
|
||||
}
|
||||
|
||||
@@ -29,7 +30,7 @@ class MongoService {
|
||||
let mongourl = `${opts.protocol || 'mongodb'}://`;
|
||||
if (opts.username) mongourl += `${opts.username}:${encodeURIComponent(opts.password)}@`;
|
||||
mongourl += `${opts.host}:${opts.port}/${opts.name}`;
|
||||
if (opts.url) mongourl = opts.url;
|
||||
if (opts.url || opts.uri) mongourl = opts.url || opts.uri;
|
||||
return mongourl;
|
||||
}
|
||||
|
||||
@@ -42,7 +43,12 @@ class MongoService {
|
||||
mongourl = mongod.getUri();
|
||||
}
|
||||
this.opts = opts;
|
||||
this.client = await MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopology: true });
|
||||
this.client = await MongoClient.connect(mongourl, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
readPreference: 'nearest',
|
||||
writeConcern: 'majority',
|
||||
});
|
||||
this.db = this.client.db(opts.name || 'hydro');
|
||||
await bus.parallel('database/connect', this.db);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export type NestKeys<O, T = any, S = any[]> = O extends object ? O extends S ? n
|
||||
export type Value<O, V = ''> = {
|
||||
[K in keyof O]: V
|
||||
};
|
||||
export type Projection<O> = (string & keyof O)[];
|
||||
export type Projection<O> = readonly (string & keyof O)[];
|
||||
export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||
export type Filter<T, U> = T extends U ? T : never;
|
||||
export type MaybeArray<T> = T | T[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function buildProjection(fields: Array<string | number>): Record<string, 1> {
|
||||
export function buildProjection(fields: readonly (string | number)[]): Record<string, 1> {
|
||||
const o = {};
|
||||
for (const k of fields) o[k] = 1;
|
||||
return o;
|
||||
|
||||
Reference in new issue
Block a user