diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c79a14cba..7d9e4fc51 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: run: | git config --global user.name undefined git config --global user.email i@undefined.moe - yarn set version stable + yarn set version 4.13.0 yarn yarn dedupe node build/prepare.js diff --git a/build/prepare.js b/build/prepare.js index 60cc902af..ba42f9a0f 100644 --- a/build/prepare.js +++ b/build/prepare.js @@ -90,6 +90,8 @@ const UIConfig = { 'packages/ui-default/backendlib/misc.ts', 'packages/ui-default/backendlib/template.ts', 'packages/ui-default/backendlib/markdown.js', + 'packages/ui-default/service-worker.ts', + 'packages/ui-default/components/message/worker.ts', '**/node_modules', ], include: ['ts', 'tsx', 'vue', 'json'] diff --git a/framework/framework/api.ts b/framework/framework/api.ts index 7da2e5f36..54b522f8b 100644 --- a/framework/framework/api.ts +++ b/framework/framework/api.ts @@ -75,19 +75,22 @@ type Projection = S extends ProjectionSchemaId [K in keyof T & keyof S]: K extends keyof AsKeys ? Projection[K]> : never }; -export const projection = >(input: T, schema: S): Projection => { +export const projection = >(input: T, schema: S, serializeCtx?: any): Projection => { if (typeof input !== 'object' || input === null) throw new Error('Input must be an object.'); type R = Projection; + if ('serialize' in input && typeof (input as any).serialize === 'function') { + input = (input as any).serialize(serializeCtx); + } if (Array.isArray(schema)) schema = Object.fromEntries(schema.map((s) => [s, 1])) as S; if (Array.isArray(input)) { - return input.map((item) => projection(item, schema)) as R; + return input.map((item) => projection(item, schema, serializeCtx)) as R; } const result = {} as R; - for (const key of Reflect.ownKeys(input)) { + for (const key of Reflect.ownKeys(input as any)) { const schemaIt = schema[key]; if (!schemaIt) continue; if (schemaIt === 1 || !input[key]) result[key] = input[key]; - else result[key] = projection(input[key], schemaIt); + else result[key] = projection(input[key], schemaIt, serializeCtx); } return result; }; @@ -174,7 +177,7 @@ export class ApiService extends Service { } } } - return (project && typeof result === 'object' && result !== null) ? projection(result, project) : result; + return (project && typeof result === 'object' && result !== null) ? projection(result, project, context) : result; } } diff --git a/packages/hydrooj/src/model/user.ts b/packages/hydrooj/src/model/user.ts index 464ff5d75..86d5d2aef 100644 --- a/packages/hydrooj/src/model/user.ts +++ b/packages/hydrooj/src/model/user.ts @@ -1,7 +1,6 @@ import { escapeRegExp, omit, pick, uniq } from 'lodash'; import { LRUCache } from 'lru-cache'; import { Collection, Filter, ObjectId } from 'mongodb'; -import { serializer } from '@hydrooj/framework'; import { LoginError, UserAlreadyExistError, UserNotFoundError } from '../error'; import { Authenticator, BaseUserDict, FileInfo, GDoc, @@ -167,11 +166,16 @@ export class User { return type === 'public' ? fields : fields.concat(this._privateFields); } - serialize(h) { - if (!this._isPrivate) { - return pick(this, this.getFields(h?.user?.hasPerm(PERM.PERM_VIEW_USER_PRIVATE_INFO) ? 'private' : 'public')); + serialize(h?) { + if (this._isPrivate) { + const result: any = {}; + for (const key of Object.keys(this)) { + if (key.startsWith('_') && key !== '_id') continue; + result[key] = this[key]; + } + return result; } - return JSON.stringify(this, serializer(true, h)); + return pick(this, this.getFields(h?.user?.hasPerm(PERM.PERM_VIEW_USER_PRIVATE_INFO) ? 'private' : 'public')); } } diff --git a/packages/ui-default/backendlib/template.ts b/packages/ui-default/backendlib/template.ts index 30d2079f0..c3fd8b874 100644 --- a/packages/ui-default/backendlib/template.ts +++ b/packages/ui-default/backendlib/template.ts @@ -18,9 +18,10 @@ let { template } = argv.options; if (!template || typeof template !== 'string') template = findFileSync('@hydrooj/ui-default/templates'); else template = findFileSync(template); -const replacer = (k, v) => { +const replacer = (s) => (k, v) => { if (k.startsWith('_') && k !== '_id') return undefined; if (typeof v === 'bigint') return `BigInt::${v.toString()}`; + if (v && typeof v === 'object' && 'serialize' in v && typeof v.serialize === 'function') return v.serialize(s); return v; }; @@ -68,7 +69,7 @@ class Nunjucks extends nunjucks.Environment { callback(error); } }, true); - this.addFilter('json', (self) => (self ? JSON.stringify(self, replacer) : '')); + this.addFilter('json', (self, s) => (self ? JSON.stringify(self, replacer(s)) : '')); this.addFilter('parseYaml', (self) => yaml.load(self)); this.addFilter('dumpYaml', (self) => yaml.dump(self)); this.addFilter('assign', (self, data) => Object.assign(self, data)); @@ -79,7 +80,7 @@ class Nunjucks extends nunjucks.Environment { this.addFilter('base64_decode', (s) => Buffer.from(s, 'base64').toString()); this.addFilter('jsesc', (self) => jsesc(self, { isScriptContext: true })); this.addFilter('bitand', (self, val) => self & val); - this.addFilter('toString', (self) => (typeof self === 'string' ? self : JSON.stringify(self, replacer))); + this.addFilter('toString', (self, s) => (typeof self === 'string' ? self : JSON.stringify(self, replacer(s)))); this.addFilter('content', (content, language, html) => { let s: any = ''; try {