diff --git a/packages/hydrooj/src/entry/common.ts b/packages/hydrooj/src/entry/common.ts index 6eaac22c1..19af22ecc 100644 --- a/packages/hydrooj/src/entry/common.ts +++ b/packages/hydrooj/src/entry/common.ts @@ -7,7 +7,7 @@ import yaml from 'js-yaml'; import { Logger } from '../logger'; import * as bus from '../service/bus'; -const logger = new Logger('common', true); +const logger = new Logger('common'); export const builtinLib = [ 'jwt', 'download', 'i18n', 'mail', 'useragent', diff --git a/packages/hydrooj/src/handler/record.ts b/packages/hydrooj/src/handler/record.ts index c4ebea8ec..4f1ac33cd 100644 --- a/packages/hydrooj/src/handler/record.ts +++ b/packages/hydrooj/src/handler/record.ts @@ -2,8 +2,8 @@ import { FilterQuery, ObjectID } from 'mongodb'; import { pick } from 'lodash'; import { postJudge } from './judge'; import { - ContestNotFoundError, PermissionError, ProblemNotFoundError, - RecordNotFoundError, UserNotFoundError, + ContestNotAttendedError, ContestNotFoundError, PermissionError, + ProblemNotFoundError, RecordNotFoundError, UserNotFoundError, } from '../error'; import { buildProjection } from '../utils'; import { RecordDoc } from '../interface'; @@ -41,6 +41,9 @@ class RecordListHandler extends Handler { tdoc = await contest.get(domainId, tid, -1); if (!tdoc) throw new ContestNotFoundError(domainId, pid); if (!contest.canShowScoreboard.call(this, tdoc, true)) throw new PermissionError(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD); + if (!this.user.own(tdoc) && !(await contest.getStatus(domainId, tid, this.user._id))?.attend) { + throw new ContestNotAttendedError(domainId, tid); + } } if (uidOrName) { const udoc = await user.getById(domainId, +uidOrName) ?? await user.getByUname(domainId, uidOrName); @@ -59,16 +62,16 @@ class RecordListHandler extends Handler { } let cursor = record.getMulti(domainId, q).sort('_id', -1); if (!full) cursor = cursor.project(buildProjection(record.PROJECTION_LIST)); - const [rdocs] = invalid ? [[]] : await paginate(cursor, page, full ? 10 : system.get('pagination.record')); + const [rdocs] = invalid ? [[] as RecordDoc[]] : await paginate(cursor, page, full ? 10 : system.get('pagination.record')); const canViewProblem = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM); - const canViewProblemHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); + const canViewProblemHidden = (!!tid) || this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN); const [udict, pdict] = full ? [{}, {}] : await Promise.all([ user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)), canViewProblem ? problem.getList(domainId, rdocs.map( (rdoc) => (rdoc.domainId === domainId ? rdoc.pid : `${rdoc.pdomain}:${rdoc.pid}`), - ), (!!tid) || canViewProblemHidden || this.user._id, false) + ), canViewProblemHidden || this.user._id, false) : Object.fromEntries([rdocs.map((rdoc) => [rdoc.pid, { ...problem.default, pid: rdoc.pid }])]), ]); this.response.body = { diff --git a/packages/hydrooj/src/logger.ts b/packages/hydrooj/src/logger.ts index 15d99d078..150786a83 100644 --- a/packages/hydrooj/src/logger.ts +++ b/packages/hydrooj/src/logger.ts @@ -1,5 +1,5 @@ -import { inspect, format, InspectOptions } from 'util'; -import { Time } from './utils'; +import { nanoid } from 'nanoid'; +import { format, inspect, InspectOptions } from 'util'; const colors = [ 20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, @@ -20,9 +20,7 @@ export class Logger { static readonly WARN = 2; static readonly DEBUG = 3; static baseLevel = process.env.DEV ? 3 : 2; - static showDiff = false; static levels: Record = {}; - static lastTime = 0; private code: number; private displayName: string; public stream: NodeJS.WritableStream = process.stderr; @@ -41,7 +39,7 @@ export class Logger { return `\u001B[3${code < 8 ? code : `8;5;${code}`}${decoration}m${value}\u001B[0m`; } - constructor(public name: string, private showDiff = false) { + constructor(public name: string) { if (name in instances) return instances[name]; let hash = 0; for (let i = 0; i < name.length; i++) { @@ -59,13 +57,32 @@ export class Logger { } private color(value: any, decoration = '') { - return Logger.color(this.code, value, decoration); + if (!process.stderr.isTTY) return value; + return `\u001B[3${this.code < 8 ? this.code : `8;5;${this.code}`}${decoration}m${value}\u001B[0m`; } private createMethod(name: LogType, prefix: string, minLevel: number) { this[name] = (...args: [any, ...any[]]) => { if (this.level < minLevel) return false; - const msg = `${prefix} ${this.displayName} ${this.format(...args)}`; + if (typeof args[0] === 'string' && /^[a-zA-Z]+\.[a-zA-Z]+(\.[a-zA-Z]+)?$/.test(args[0])) { + const [type, ...payload] = args; + if (global.Hydro.service.db?.started) { + global.Hydro.service.db.collection('log').insertOne({ + _id: nanoid(), + _pid: process.pid, + _time: new Date(), + _level: name, + _category: this.name, + _message: type, + ...payload, + }); + } + const msg = `${prefix} ${this.displayName} ${type.translate('en').format({ _inspect: true, payload })}`; + if (process.send) process.send({ event: 'message/log', payload: [msg] }); + else global.Hydro.service.bus.parallel('message/log', msg); + return true; + } + const msg = `${prefix} ${this.displayName} ${this.format(...args)})}`; if (process.send) process.send({ event: 'message/log', payload: [msg] }); else global.Hydro.service.bus.parallel('message/log', msg); return true; @@ -76,8 +93,6 @@ export class Logger { return Logger.levels[this.name] ?? Logger.baseLevel; } - extend = (namespace: string, showDiff = this.showDiff) => new Logger(`${this.name}:${namespace}`, showDiff); - format: (format: any, ...param: any[]) => string = (...args) => { if (args[0] instanceof Error) args[0] = args[0].stack || args[0].message; else if (typeof args[0] !== 'string') args.unshift('%O'); @@ -93,13 +108,6 @@ export class Logger { } return match; }).split('\n').join('\n '); - if (Logger.showDiff || this.showDiff) { - const now = Date.now(); - if (Logger.lastTime) { - args.push(this.color(`+${Time.formatTimeShort(now - Logger.lastTime)}`)); - } - Logger.lastTime = now; - } return format(...args); }; } diff --git a/packages/hydrooj/src/service/bus.ts b/packages/hydrooj/src/service/bus.ts index b56b567a6..0ddaff6d7 100644 --- a/packages/hydrooj/src/service/bus.ts +++ b/packages/hydrooj/src/service/bus.ts @@ -17,7 +17,7 @@ import type { ProblemSolutionHandler } from '../handler/problem'; import type { UserRegisterHandler } from '../handler/user'; const _hooks: Record any>> = {}; -const logger = new Logger('bus', true); +const logger = new Logger('bus'); const argv = cac().parse(); function isBailed(value: any) { diff --git a/packages/utils/lib/utils.ts b/packages/utils/lib/utils.ts index 2ca233820..1c93ab476 100644 --- a/packages/utils/lib/utils.ts +++ b/packages/utils/lib/utils.ts @@ -5,6 +5,7 @@ import { Duplex } from 'stream'; import { ObjectID } from 'mongodb'; import { isMoment } from 'moment'; import type { Moment } from 'moment-timezone'; +import { inspect } from 'util'; declare global { interface StringConstructor { @@ -41,12 +42,16 @@ String.random = function random(digit = 32, dict = defaultDict) { String.prototype.format = function formatStr(...args) { let result = this; - if (args.length > 0) { - if (args.length === 1 && typeof (args[0]) === 'object') { - for (const key in args) { - if (args[key] !== undefined) { + if (args.length) { + if (args.length === 1 && typeof args[0] === 'object') { + const t = args[0]; + for (const key in t) { + if (!key.startsWith('_') && t[key] !== undefined) { + if (t._inspect && typeof t[key] === 'object') { + t[key] = inspect(t[key], { colors: process?.stderr?.isTTY }); + } const reg = new RegExp(`(\\{${key}\\})`, 'g'); - result = result.replace(reg, args[key]); + result = result.replace(reg, t[key]); } } } else return this.formatFromArray(args);