diff --git a/framework/framework/package.json b/framework/framework/package.json index b6d2f4e62..b17d0cdb9 100644 --- a/framework/framework/package.json +++ b/framework/framework/package.json @@ -26,7 +26,7 @@ "@types/koa": "^2.15.0", "@types/koa-compress": "^4.0.6", "@types/koa-router": "^7.4.8", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/saslprep": "^1.0.3", "@types/ws": "^8.5.10" } diff --git a/package.json b/package.json index 018ce99eb..43f07faca 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "ora": "^8.0.1", "package-json": "^10.0.0", "semver": "^7.6.2", - "simple-git": "^3.24.0", + "simple-git": "^3.25.0", "supertest": "^7.0.0", "typescript": "5.3.3", "wtfnode": "^0.9.2" diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index a5a0e2185..25cc6c4fd 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -5,11 +5,11 @@ "main": "react.yaml", "dependencies": { "@stylistic/eslint-plugin": "^2.1.0", - "@typescript-eslint/eslint-plugin": "^7.12.0", - "@typescript-eslint/parser": "^7.12.0", + "@typescript-eslint/eslint-plugin": "^7.13.0", + "@typescript-eslint/parser": "^7.13.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-plugin-github": "^4.10.2", + "eslint-plugin-github": "^5.0.1", "eslint-plugin-import": "2.29.1", "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.34.2", diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 7725cc6ae..209eecf7d 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -12,10 +12,10 @@ }, "preferUnplugged": true, "dependencies": { - "@aws-sdk/client-s3": "3.591.0", - "@aws-sdk/lib-storage": "3.591.0", - "@aws-sdk/s3-presigned-post": "3.591.0", - "@aws-sdk/s3-request-presigner": "3.591.0", + "@aws-sdk/client-s3": "3.596.0", + "@aws-sdk/lib-storage": "3.596.0", + "@aws-sdk/s3-presigned-post": "3.596.0", + "@aws-sdk/s3-request-presigner": "3.596.0", "@graphql-tools/schema": "^10.0.4", "@hydrooj/framework": "workspace:^", "@hydrooj/utils": "workspace:^", @@ -28,7 +28,7 @@ "emoji-regex": "^10.3.0", "emojis-list": "2.1.0", "fs-extra": "^11.2.0", - "graphql": "^16.8.1", + "graphql": "^16.8.2", "graphql-scalars": "^1.23.0", "js-yaml": "^4.1.0", "koa-proxies": "^0.12.4", @@ -59,7 +59,7 @@ "@types/fs-extra": "^11.0.4", "@types/js-yaml": "^4.0.9", "@types/koa-static-cache": "^5.1.4", - "@types/lodash": "^4.17.4", + "@types/lodash": "^4.17.5", "@types/mime-types": "^2.1.4", "@types/mongodb-uri": "^0.9.4", "@types/nodemailer": "^6.4.15", diff --git a/packages/hydrooj/src/handler/domain.ts b/packages/hydrooj/src/handler/domain.ts index 6bc1adcb5..438ff8629 100644 --- a/packages/hydrooj/src/handler/domain.ts +++ b/packages/hydrooj/src/handler/domain.ts @@ -8,7 +8,6 @@ import { } from '../error'; import type { DomainDoc } from '../interface'; import avatar from '../lib/avatar'; -import paginate from '../lib/paginate'; import { PERM, PERMS_BY_FAMILY, PRIV } from '../model/builtin'; import * as discussion from '../model/discussion'; import domain from '../model/domain'; @@ -24,10 +23,10 @@ import { log2 } from '../utils'; class DomainRankHandler extends Handler { @query('page', Types.PositiveInt, true) async get(domainId: string, page = 1) { - const [dudocs, upcount, ucount] = await paginate( + const [dudocs, upcount, ucount] = await this.paginate( domain.getMultiUserInDomain(domainId, { uid: { $gt: 1 }, rp: { $gt: 0 } }).sort({ rp: -1 }), page, - 100, + 'ranking', ); const udict = await user.getList(domainId, dudocs.map((dudoc) => dudoc.uid)); const udocs = dudocs.map((i) => udict[i.uid]); diff --git a/packages/hydrooj/src/interface.ts b/packages/hydrooj/src/interface.ts index 45e0d3bfc..5ce54e0de 100644 --- a/packages/hydrooj/src/interface.ts +++ b/packages/hydrooj/src/interface.ts @@ -777,11 +777,10 @@ export interface ProblemSearchOptions { export type ProblemSearch = (domainId: string, q: string, options?: ProblemSearchOptions) => Promise; export interface Lib extends Record { - difficulty: typeof import('./lib/difficulty'); + difficulty: typeof import('./lib/difficulty').default; buildContent: typeof import('./lib/content').buildContent; mail: typeof import('./lib/mail'); - rank: typeof import('./lib/rank'); - rating: typeof import('./lib/rating'); + rating: typeof import('./lib/rating').default; testdataConfig: typeof import('./lib/testdataConfig'); problemSearch: ProblemSearch; } diff --git a/packages/hydrooj/src/lib/difficulty.ts b/packages/hydrooj/src/lib/difficulty.ts index 87d924d34..231376806 100644 --- a/packages/hydrooj/src/lib/difficulty.ts +++ b/packages/hydrooj/src/lib/difficulty.ts @@ -42,5 +42,5 @@ function difficultyAlgorithm(nSubmit: number, nAccept: number) { return Math.max(ans, 1); } -export = difficultyAlgorithm; +export default difficultyAlgorithm; global.Hydro.lib.difficulty = difficultyAlgorithm; diff --git a/packages/hydrooj/src/lib/paginate.ts b/packages/hydrooj/src/lib/paginate.ts deleted file mode 100644 index 59b3b7f79..000000000 --- a/packages/hydrooj/src/lib/paginate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { FindCursor } from 'mongodb'; -import { ValidationError } from '../error'; -import db from '../service/db'; - -async function paginate( - cursor: FindCursor, page: number, pageSize: number, -): Promise<[docs: T[], numPages: number, count: number]> { - if (page <= 0) throw new ValidationError('page'); - let filter = {}; - for (const key of Object.getOwnPropertySymbols(cursor)) { - if (key.toString() !== 'Symbol(filter)') continue; - filter = cursor[key]; - break; - } - const coll = db.collection(cursor.namespace.collection as any); - const [count, pageDocs] = await Promise.all([ - Object.keys(filter).length ? coll.count(filter) : coll.countDocuments(filter), - cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(), - ]); - const numPages = Math.floor((count + pageSize - 1) / pageSize); - return [pageDocs, numPages, count]; -} - -export = paginate; diff --git a/packages/hydrooj/src/lib/rank.ts b/packages/hydrooj/src/lib/rank.ts deleted file mode 100644 index 2f6fc17a9..000000000 --- a/packages/hydrooj/src/lib/rank.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { FindCursor } from 'mongodb'; - -type ranked = >(cursor: FindCursor, equ: ((a: T, b: T) => boolean)) => Promise<[number, T][]>; - -const ranked: ranked = async (cursor, equ) => { - let last = null; - let r = 0; - let count = 0; - const results = []; - const docs = await cursor.toArray(); - for (const doc of docs) { - if (doc.unrank) { - results.push([0, doc]); - continue; - } - count++; - if (!last || !equ(last, doc)) r = count; - last = doc; - results.push([r, doc]); - } - return results; -}; - -global.Hydro.lib.rank = ranked; -export = ranked; diff --git a/packages/hydrooj/src/lib/rating.ts b/packages/hydrooj/src/lib/rating.ts index 029cf8030..5c89c6678 100644 --- a/packages/hydrooj/src/lib/rating.ts +++ b/packages/hydrooj/src/lib/rating.ts @@ -118,6 +118,6 @@ function calculate(users: RatingInputUser[]): RatingOutputUser[] { return calculator.calculate() as RatingOutputUser[]; } -export = calculate; +export default calculate; global.Hydro.lib.rating = calculate; diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 1e208bff3..03def05d9 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -9,7 +9,6 @@ import { BaseUserDict, ContestRule, ContestRules, ProblemDict, RecordDoc, ScoreboardConfig, ScoreboardNode, ScoreboardRow, SubtaskResult, Tdoc, } from '../interface'; -import ranked from '../lib/rank'; import * as bus from '../service/bus'; import db from '../service/db'; import type { Handler } from '../service/server'; @@ -224,7 +223,7 @@ const acm = buildContestRule({ return row; }, async scoreboard(config, _, tdoc, pdict, cursor) { - const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score && a.time === b.time); + const rankedTsdocs = await db.ranked(cursor, (a, b) => a.score === b.score && a.time === b.time); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getListForRender(tdoc.domainId, uids, config.showDisplayName); // Find first accept @@ -257,7 +256,7 @@ const acm = buildContestRule({ return [rows, udict]; }, async ranked(tdoc, cursor) { - return await ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time); + return await db.ranked(cursor, (a, b) => a.accept === b.accept && a.time === b.time); }, applyProjection(tdoc, rdoc) { if (isDone(tdoc)) return rdoc; @@ -394,7 +393,7 @@ const oi = buildContestRule({ return row; }, async scoreboard(config, _, tdoc, pdict, cursor) { - const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score); + const rankedTsdocs = await db.ranked(cursor, (a, b) => a.score === b.score); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getListForRender(tdoc.domainId, uids, config.showDisplayName); const psdict = {}; @@ -431,7 +430,7 @@ const oi = buildContestRule({ return [rows, udict]; }, async ranked(tdoc, cursor) { - return await ranked(cursor, (a, b) => a.score === b.score); + return await db.ranked(cursor, (a, b) => a.score === b.score); }, applyProjection(tdoc, rdoc) { if (isDone(tdoc)) return rdoc; @@ -744,7 +743,7 @@ const homework = buildContestRule({ return row; }, async scoreboard(config, _, tdoc, pdict, cursor) { - const rankedTsdocs = await ranked(cursor, (a, b) => a.score === b.score); + const rankedTsdocs = await db.ranked(cursor, (a, b) => a.score === b.score); const uids = rankedTsdocs.map(([, tsdoc]) => tsdoc.uid); const udict = await user.getListForRender(tdoc.domainId, uids, config.showDisplayName); const columns = await this.scoreboardHeader(config, _, tdoc, pdict); @@ -757,7 +756,7 @@ const homework = buildContestRule({ return [rows, udict]; }, async ranked(tdoc, cursor) { - return await ranked(cursor, (a, b) => a.score === b.score); + return await db.ranked(cursor, (a, b) => a.score === b.score); }, }); diff --git a/packages/hydrooj/src/plugin-api.ts b/packages/hydrooj/src/plugin-api.ts index c58de3abf..fafe3f107 100644 --- a/packages/hydrooj/src/plugin-api.ts +++ b/packages/hydrooj/src/plugin-api.ts @@ -43,8 +43,10 @@ export { buildContent } from './lib/content'; export { default as mime } from './lib/mime'; export { default as rating } from './lib/rating'; export { default as avatar } from './lib/avatar'; -export { default as rank } from './lib/rank'; -export { default as paginate } from './lib/paginate'; +/** @deprecated use Handler.paginate instead */ +export const paginate = db.paginate.bind(db); +/** @deprecated use db.ranked instead */ +export const rank = db.ranked.bind(db); export { UiContextBase } from './service/layers/base'; export * from '@hydrooj/framework/decorators'; export * from '@hydrooj/framework/validator'; diff --git a/packages/hydrooj/src/service/db.ts b/packages/hydrooj/src/service/db.ts index 6e9768708..9430b9ea6 100644 --- a/packages/hydrooj/src/service/db.ts +++ b/packages/hydrooj/src/service/db.ts @@ -1,9 +1,10 @@ /* eslint-disable no-await-in-loop */ import { - Collection, Db, IndexDescription, MongoClient, + Collection, Db, FindCursor, IndexDescription, MongoClient, } from 'mongodb'; import mongoUri from 'mongodb-uri'; import { Time } from '@hydrooj/utils'; +import { ValidationError } from '../error'; import { Logger } from '../logger'; import { load } from '../options'; import * as bus from './bus'; @@ -102,6 +103,44 @@ class MongoService { } } + async paginate( + cursor: FindCursor, page: number, pageSize: number, + ): Promise<[docs: T[], numPages: number, count: number]> { + if (page <= 0) throw new ValidationError('page'); + let filter = {}; + for (const key of Object.getOwnPropertySymbols(cursor)) { + if (key.toString() !== 'Symbol(filter)') continue; + filter = cursor[key]; + break; + } + const coll = this.db.collection(cursor.namespace.collection as any); + const [count, pageDocs] = await Promise.all([ + Object.keys(filter).length ? coll.count(filter) : coll.countDocuments(filter), + cursor.skip((page - 1) * pageSize).limit(pageSize).toArray(), + ]); + const numPages = Math.floor((count + pageSize - 1) / pageSize); + return [pageDocs, numPages, count]; + } + + async ranked>(cursor: FindCursor, equ: (a: T, b: T) => boolean): Promise<[number, T][]> { + let last = null; + let r = 0; + let count = 0; + const results = []; + const docs = await cursor.toArray(); + for (const doc of docs) { + if ((doc as any).unrank) { + results.push([0, doc]); + continue; + } + count++; + if (!last || !equ(last, doc)) r = count; + last = doc; + results.push([r, doc]); + } + return results; + } + public async apply(ctx) { await this.start(); ctx.on('dispose', () => this.client.close()); diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 85394d15d..d75126913 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -11,13 +11,13 @@ import { errorMessage, Time } from '@hydrooj/utils'; import { Context } from '../context'; import { PermissionError, PrivilegeError } from '../error'; import type { DomainDoc } from '../interface'; -import paginate from '../lib/paginate'; import { Logger } from '../logger'; import { PERM, PRIV } from '../model/builtin'; import * as opcount from '../model/opcount'; import * as OplogModel from '../model/oplog'; import * as system from '../model/system'; import { builtinConfig } from '../settings'; +import db from './db'; import baseLayer from './layers/base'; import domainLayer from './layers/domain'; import userLayer from './layers/user'; @@ -228,7 +228,7 @@ export async function apply(ctx: Context) { return res; }, paginate(cursor: FindCursor, page: number, key: string) { - return paginate(cursor, page, this.ctx.setting.get(`pagination.${key}`)); + return db.paginate(cursor, page, this.ctx.setting.get(`pagination.${key}`)); }, checkPerm(...args: bigint[]) { if (!this.user.hasPerm(...args)) { diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 077d83cdc..568215c5a 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -25,7 +25,7 @@ "@fontsource/source-code-pro": "^5.0.18", "@fontsource/ubuntu-mono": "^5.0.20", "@hydrooj/utils": "workspace:^", - "@sentry/browser": "^8.8.0", + "@sentry/browser": "^8.9.2", "@sentry/webpack-plugin": "^2.18.0", "@simplewebauthn/browser": "7.4.0", "@svgr/webpack": "^8.1.0", @@ -120,7 +120,7 @@ "vditor": "^3.10.4", "vinyl-buffer": "^1.0.1", "web-streams-polyfill": "^4.0.0", - "webpack": "^5.91.0", + "webpack": "^5.92.0", "webpack-bundle-analyzer": "^4.10.2", "webpack-dev-server": "^5.0.4", "webpack-manifest-plugin": "^5.0.0", @@ -136,7 +136,7 @@ "katex": "^0.16.10", "lodash": "^4.17.21", "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", + "markdown-it-anchor": "^9.0.1", "markdown-it-attrs": "^4.1.6", "markdown-it-footnote": "^4.0.0", "markdown-it-imsize": "^2.0.1", diff --git a/packages/utils/package.json b/packages/utils/package.json index b8a1c9ef9..7273454a3 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -16,7 +16,7 @@ "mongodb": "^5.9.2", "reggol": "^1.6.3", "search-query-parser": "^1.6.0", - "systeminformation": "^5.22.10" + "systeminformation": "^5.22.11" }, "devDependencies": { "@types/fs-extra": "^11.0.4"