diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 0745bb3f4..7ecb9e1a8 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -465,7 +465,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); }); await new Promise((r) => { httpServer.listen(this.config.port, this.config.host || '127.0.0.1', () => { - logger.success('Server listening at: %d', this.config.port); + logger.success('Server listening at: %c', `${this.config.host || '127.0.0.1'}:${this.config.port}`); r(true); }); }); @@ -685,6 +685,10 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); }; }; + // if (name === 'ContestScoreboard') { + // console.log('-------------', this.ctx, this.ctx.scoreboard); + // } + if (type === 'route') { router.all(routeName, path, (ctx) => this.handleHttp(ctx as any, HandlerClass, Checker(permPrivChecker))); } else { @@ -707,6 +711,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); public withHandlerClass( name: T, callback: (HandlerClass: T extends `${string}ConnectionHandler` ? typeof ConnectionHandler : typeof Handler) => any, ) { + name = name.replace(/Handler$/, '') as any; if (this.registry[name]) callback(this.registry[name]); // FIXME: should pass type check this.ctx.on(`handler/register/${name}`, callback as any); @@ -714,6 +719,10 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); // eslint-disable-next-line @typescript-eslint/naming-convention public Route(name: string, path: string, RouteHandler: typeof Handler, ...permPrivChecker) { + // if (name === 'contest_scoreboard') { + // console.log('+++', this.ctx); + // console.log(this.ctx.scoreboard); + // } return this.register('route', name, path, RouteHandler, ...permPrivChecker); } diff --git a/packages/hydrooj/bin/hydrooj.js b/packages/hydrooj/bin/hydrooj.js index bad5d596e..3e9d7c082 100755 --- a/packages/hydrooj/bin/hydrooj.js +++ b/packages/hydrooj/bin/hydrooj.js @@ -6,13 +6,19 @@ const { homedir } = require('os'); const { default: hook } = require('@undefined-moe/require-resolve-hook'); const { bypass } = hook(/^(hydrooj|@hydrooj|cordis|lodash|js-yaml)($|\/)/, (id) => { if (id.startsWith('hydrooj/src')) { - console.log('module require via %s is deprecated.', id); if (process.env.DEV) { - console.log( - new Error().stack.split('\n') - .filter((i) => !i.includes('node:internal') && i.startsWith(' ')) - .join('\n'), - ); + const error = new Error(`module require via ${id} is deprecated.`); + const filter = [ + 'node:diagnostics', 'node:internal', + '_resolveFilename', 'ignoreModuleNotFoundError', + 'framework/register', '@hydrooj/register', + ]; + error.stack = error.stack.split('\n') + .filter((i) => !filter.some((f) => i.includes(f))) + .join('\n'); + console.error(error); + } else { + console.warn('Module require via %s is deprecated.', id); } } if (id.startsWith('hydrooj')) { diff --git a/packages/hydrooj/src/lib/i18n.ts b/packages/hydrooj/src/lib/i18n.ts index 2652f1559..2965ceb89 100644 --- a/packages/hydrooj/src/lib/i18n.ts +++ b/packages/hydrooj/src/lib/i18n.ts @@ -42,7 +42,7 @@ class I18nService extends Service { if (languages[0]?.startsWith('en')) { // For most use cases, source text equals to translated text in English. // So if it doesn't exist, we should use the original text instead of fallback. - return this.get(str, languages[0]) || this.get(str, 'en') || this.toString(); + return this.get(str, languages[0]) || this.get(str, 'en') || str.toString(); } for (const language of languages.filter(Boolean)) { const curr = this.get(str, language) || this.get(str, language.split('_')[0]) diff --git a/packages/hydrooj/src/model/contest.ts b/packages/hydrooj/src/model/contest.ts index 78110af95..e1680a902 100644 --- a/packages/hydrooj/src/model/contest.ts +++ b/packages/hydrooj/src/model/contest.ts @@ -388,7 +388,7 @@ const oi = buildContestRule({ score: tsddict[pid]?.score, }; if (tsddict[pid]?.status === STATUS.STATUS_ACCEPTED) { - if (tsddict[pid].rid.getTimestamp().getTime() - (tsdoc.startAt.getTime() || tdoc.beginAt.getTime())) { + if (tsddict[pid].rid.getTimestamp().getTime() - (tsdoc.startAt || tdoc.beginAt).getTime() === meta?.first?.[pid]) { node.style = 'background-color: rgb(217, 240, 199);'; } } @@ -403,9 +403,9 @@ const oi = buildContestRule({ const psdict = {}; const first = {}; for (const [, tsdoc] of rankedTsdocs) { - for (const [pid, detail] of Object.entries(tsdoc.detail)) { + for (const [pid, detail] of Object.entries(tsdoc.detail || {})) { if (detail.status !== STATUS.STATUS_ACCEPTED) continue; - const time = detail.rid.getTimestamp().getTime() - (tsdoc.startAt.getTime() || tdoc.beginAt.getTime()); + const time = detail.rid.getTimestamp().getTime() - (tsdoc.startAt || tdoc.beginAt).getTime(); if (!first[pid] || first[pid] > time) first[pid] = time; } } diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 2853c3226..eb84340c1 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -222,15 +222,22 @@ export async function apply(ctx: Context) { } }, }); - server.wsHandlerMixin((s) => ({ + server.wsHandlerMixin({ async onerror(err: HydroError) { if (![NotFoundError, PrivilegeError, PermissionError].some((i) => err instanceof i) || this.user?._id !== 0) { logger.error(`Path:${this.request.path}, User:${this.user?._id}(${this.user?.uname})`); logger.error(err); } - await s.onerror(err); + if (err instanceof UserFacingError) err.stack = this.request.path; + this.send({ + error: { + name: err.name, + params: err.params || [], + }, + }); + this.close(4000, err.toString()); }, - })); + }); on('handler/create', async (h) => { h.user = h.context.HydroContext.user as any; diff --git a/packages/ui-default/backendlib/template.ts b/packages/ui-default/backendlib/template.ts index b2f4730a5..17edc6446 100644 --- a/packages/ui-default/backendlib/template.ts +++ b/packages/ui-default/backendlib/template.ts @@ -274,6 +274,3 @@ export class TemplateService extends Service { } } } - -export async function apply(ctx: Context) { -} diff --git a/packages/vjudge/src/index.ts b/packages/vjudge/src/index.ts index a35035bef..80c7dfecc 100644 --- a/packages/vjudge/src/index.ts +++ b/packages/vjudge/src/index.ts @@ -155,7 +155,9 @@ class AccountService { this.syncing = false; } - async stop() { } // eslint-disable-line @typescript-eslint/no-empty-function + async stop() { + return this.api?.stop?.(); + } async main() { const res = await this.login(); diff --git a/packages/vjudge/src/interface.ts b/packages/vjudge/src/interface.ts index 5e8405f46..24b82edab 100644 --- a/packages/vjudge/src/interface.ts +++ b/packages/vjudge/src/interface.ts @@ -41,6 +41,7 @@ export interface IBasicProvider { submitProblem(id: string, lang: string, code: string, info: any, next: NextFunction, end: NextFunction): Promise; waitForSubmission(id: string, next: NextFunction, end: NextFunction): Promise; checkStatus?: (onCheckFunc: boolean) => Promise; + stop?: () => Promise; } export interface BasicProvider { diff --git a/test/main.ts b/test/main.ts index 0187b5715..c4d499191 100644 --- a/test/main.ts +++ b/test/main.ts @@ -30,7 +30,10 @@ describe('App', () => { }, { timeout: 30000 }); const routes = ['/', '/api', '/p', '/contest', '/homework', '/user/1', '/training']; - routes.forEach((route) => it(`GET ${route}`, () => agent.get(route).expect(200))); + for (const route of routes) { + // eslint-disable-next-line @typescript-eslint/no-loop-func + it(`GET ${route}`, () => agent.get(route).expect(200)); + } it('API user', async () => { await agent.get('/api?{user(id:1){uname}}').expect({ data: { user: { uname: 'Hydro' } } }); @@ -63,12 +66,13 @@ describe('App', () => { const results: Record = {}; if (process.env.BENCHMARK) { - routes.forEach((route) => it(`Performance test ${route}`, { timeout: 60000 }, async () => { - await global.Hydro.model.system.set('limit.global', 99999); - const result = await autocannon({ url: `http://localhost:8888${route}` }); - assert(result.errors === 0, `test ${route} returns errors`); - results[route] = result; - })); + for (const route of routes) { + it(`Performance test ${route}`, { timeout: 60000 }, async () => { + const result = await autocannon({ url: `http://localhost:8888${route}` }); + assert(result.errors === 0, `test ${route} returns errors`); + results[route] = result; + }); + } } after(() => {