diff --git a/framework/eslint-config/package.json b/framework/eslint-config/package.json index dd9caeff2..2a0163589 100644 --- a/framework/eslint-config/package.json +++ b/framework/eslint-config/package.json @@ -4,7 +4,7 @@ "license": "MIT", "main": "base.mjs", "dependencies": { - "@eslint-react/eslint-plugin": "^1.37.0", + "@eslint-react/eslint-plugin": "^1.37.3", "@eslint/compat": "^1.2.7", "@eslint/eslintrc": "^3.3.1", "@stylistic/eslint-plugin": "^4.2.0", diff --git a/framework/framework/decorators.ts b/framework/framework/decorators.ts index fdd82a9e6..83423de43 100644 --- a/framework/framework/decorators.ts +++ b/framework/framework/decorators.ts @@ -14,6 +14,8 @@ export interface ParamOption { validate?: Validator, } +const mergeValidator = (L: Validator, R: Validator) => (v: any) => L(v) && R(v); + function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ...args: Array | boolean | Validator | Converter>) { let cursor = 0; const v: ParamOption = { name, source }; @@ -27,7 +29,7 @@ function _buildParam(name: string, source: 'get' | 'post' | 'all' | 'route', ... if (type[2]) v.isOptional = type[2]; } else if (typeof current === 'boolean') v.isOptional = current; else if (isValidate) { - if (current !== null) v.validate = current; + if (current !== null) v.validate = mergeValidator(v.validate, current); isValidate = false; } else v.convert = current; cursor++; diff --git a/framework/framework/server.ts b/framework/framework/server.ts index 03493f161..db436cf35 100644 --- a/framework/framework/server.ts +++ b/framework/framework/server.ts @@ -44,10 +44,12 @@ export function encodeRFC5987ValueChars(str: string) { async function forkContextWithScope(ctx: C) { const scope = ctx.plugin(() => { }); await scope; + const dispose = () => scope.dispose(); return { scope, ctx: scope.ctx, - [Symbol.asyncDispose]: () => scope.dispose(), + dispose, + [Symbol.asyncDispose]: dispose, }; } @@ -565,11 +567,10 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); const { args } = ctx.HydroContext; const sub = await forkContextWithScope(savedContext); const h = new HandlerClass(ctx, sub.ctx); - // FIXME: should pass type check - await (this.ctx.parallel as any)('connection/create', h); - const stream = new PassThrough(); + let stream: PassThrough; if (!conn) { // By HTTP + stream = new PassThrough(); ctx.request.socket.setTimeout(0); ctx.req.socket.setNoDelay(true); ctx.req.socket.setKeepAlive(true); @@ -592,23 +593,25 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } ctx.handler = h; h.conn = conn; - const disposables = []; - let interval: NodeJS.Timeout; let closed = false; const clean = async (err?: Error) => { if (closed) return; closed = true; - if (err) await h.onerror(err); - // FIXME: should pass type check - else (this.ctx.emit as any)('connection/close', h); - h.active = false; - for (const d of disposables) d(); - if (layer) layer.clients.delete(conn); - if (err && !layer) ctx.status = 500; - if (interval) clearInterval(interval); - await h.cleanup?.(args); - await sub.scope.dispose(); + try { + try { + if (err) await h.onerror(err); + // FIXME: should pass type check + else (this.ctx.emit as any)('connection/close', h); + } finally { + h.active = false; + if (layer) layer.clients.delete(conn); + if (err && !layer) ctx.status = 500; + await h.cleanup?.(args); + } + } finally { + await sub.dispose(); + } }; try { @@ -619,10 +622,10 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); if (args.shorty) h.resetCompression(); if (h._prepare) await h._prepare(args); if (h.prepare) await h.prepare(args); - for (const { name, target } of h.__subscribe || []) disposables.push(this.ctx.on(name, target.bind(h))); + for (const { name, target } of h.__subscribe || []) sub.ctx.on(name, target.bind(h)); if (layer) { let lastHeartbeat = Date.now(); - interval = setInterval(() => { + sub.ctx.setInterval(() => { if (Date.now() - lastHeartbeat > 80000) { clean(); conn.terminate(); @@ -642,8 +645,8 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); let payload; try { payload = JSON.parse(e.data.toString()); - } catch { - conn.close(); + } catch (err) { + await clean(err); } try { await h.message?.(payload); @@ -667,6 +670,7 @@ ${c.response.status} ${endTime - startTime}ms ${c.response.length}`); } } catch (e) { // error during initialization (prepare, hooks) + logger.error(e); await clean(e); } } diff --git a/package.json b/package.json index 3b3310f0c..9a88200de 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "oxlint": "cd framework/eslint-config && node -r @hydrooj/register build.ts && cd ../../ && oxlint --config framework/eslint-config/.oxlintrc.json", "lint": "eslint --flag unstable_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --fix", "lint:ci": "eslint --flag unstable_config_lookup_from_file packages plugins modules framework --ext .ts,.tsx,.jsx --max-warnings=0", - "debug": "node --trace-warnings --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template", + "debug": "node --trace-warnings --inspect=192.168.99.234:1111 --async-stack-traces --trace-deprecation packages/hydrooj/bin/hydrooj --debug --template", "start": "packages/hydrooj/bin/hydrooj.js", "postinstall": "node build/prepare.js", "gen-patch": "node -r @hydrooj/register build/gen-patch.ts", diff --git a/packages/elastic/index.ts b/packages/elastic/index.ts index 2b3f31e0b..9f722efec 100644 --- a/packages/elastic/index.ts +++ b/packages/elastic/index.ts @@ -26,7 +26,6 @@ export default class ElasticSearchService extends Service { client: Client; constructor(ctx: Context, private config: ReturnType) { - console.log('elastic-search', config); super(ctx, 'elastic-search'); this.client = new Client({ node: config.url }); } diff --git a/packages/hydrooj/src/service/server.ts b/packages/hydrooj/src/service/server.ts index 3c24663f1..068d25c1c 100644 --- a/packages/hydrooj/src/service/server.ts +++ b/packages/hydrooj/src/service/server.ts @@ -119,6 +119,8 @@ export async function apply(ctx: Context) { server.addLayer('base', baseLayer); server.addLayer('user', userLayer); + let cachedTranslate = null; + server.handlerMixin({ url(name: string, ...kwargsList: Record[]) { if (name === '#') return '#'; @@ -159,10 +161,11 @@ export async function apply(ctx: Context) { translate(str: string) { if (!str) return ''; const lang = this.user?.viewLang || this.session?.viewLang; - const res = lang - ? ctx.i18n.translate(str.toString(), [lang, ...this.context.acceptsLanguages()]) - : ctx.i18n.translate(str.toString(), [...this.context.acceptsLanguages(), system.get('server.language')]); - return res; + const langs = lang + ? [lang, ...this.context.acceptsLanguages()] + : [...this.context.acceptsLanguages(), system.get('server.language')]; + cachedTranslate ||= ctx.i18n.translate; + return cachedTranslate(str.toString(), langs); }, paginate(cursor: FindCursor, page: number, key: string | number) { return db.paginate(cursor, page, typeof key === 'number' ? key : (this.ctx.setting.get(`pagination.${key}`) || 20)); diff --git a/packages/hydrooj/src/upgrade.ts b/packages/hydrooj/src/upgrade.ts index 7e4e53e91..5be8b0fed 100644 --- a/packages/hydrooj/src/upgrade.ts +++ b/packages/hydrooj/src/upgrade.ts @@ -710,7 +710,7 @@ export const coreScripts: MigrationScript[] = [ { $or: [{ _join: { $exists: false } }, { _join: { $eq: null } }, { '_join.method': domain.JOIN_METHOD_NONE }] }, { $set: { _join: { method: domain.JOIN_METHOD_ALL, role: 'default', expire: null }, _migratedJoin: true } }, ); - const domainUser = await domain.coll.find({ domainId: 'system', join: true }, onPrimary) + const domainUser = await domain.collUser.find({ domainId: 'system', join: true }, onPrimary) .project({ uid: 1 }).toArray(); const otherUsers = await user.coll.find({ _id: { $gt: 1, $nin: domainUser.map((u) => u.uid) } }, onPrimary) .project({ _id: 1 }).toArray(); diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 6504e1721..c136d776b 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -67,7 +67,7 @@ "jquery.easing": "^1.4.1", "jquery.transit": "^0.9.12", "matchmedia-polyfill": "^0.3.2", - "md-editor-rt": "^5.4.3", + "md-editor-rt": "^5.4.4", "mini-css-extract-plugin": "^2.9.2", "moment": "^2.30.1", "monaco-editor": "0.52.2",