diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index 3e8efd0e9..c650946b9 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.17.3", + "version": "2.17.4", "bin": "bin/hydrooj.js", "main": "dist/loader.js", "typings": "dist/loader.d.ts", diff --git a/packages/hydrooj/src/handler/problem.ts b/packages/hydrooj/src/handler/problem.ts index 489fd4a99..43d5086b9 100644 --- a/packages/hydrooj/src/handler/problem.ts +++ b/packages/hydrooj/src/handler/problem.ts @@ -480,11 +480,8 @@ export class ProblemFilesHandler extends ProblemDetailHandler { @post('type', Types.Range(['testdata', 'additional_file']), true) async postDeleteFiles(domainId: string, files: string[], type = 'testdata') { if (this.pdoc.owner !== this.user._id) this.checkPerm(PERM.PERM_EDIT_PROBLEM); - if (type === 'testdata') { - await problem.delTestdata(domainId, this.pdoc.docId, files); - } else { - await storage.del(files.map((file) => `problem/${this.pdoc.domainId}/${this.pdoc.docId}/${type}/${file}`)); - } + if (type === 'testdata') await problem.delTestdata(domainId, this.pdoc.docId, files); + else await problem.delAdditionalFile(domainId, this.pdoc.docId, files); this.back(); } } diff --git a/packages/hydrooj/src/model/document.ts b/packages/hydrooj/src/model/document.ts index 79bee756b..0b4b20f06 100644 --- a/packages/hydrooj/src/model/document.ts +++ b/packages/hydrooj/src/model/document.ts @@ -200,13 +200,14 @@ export async function pull( - domainId: string, docType: K, docId: DocType[K]['docId'], - key: ArrayKeys, subId: ObjectID, -): Promise { +export async function deleteSub>( + domainId: string, docType: T, docId: DocType[T]['docId'], + key: K, subId: DocType[T][K][0]['_id'] | DocType[T][K][0]['_id'][], +): Promise { + subId = (subId instanceof Array) ? subId : [subId]; const res = await coll.findOneAndUpdate( { domainId, docType, docId }, - { $pull: { [key]: { _id: subId } } }, + { $pull: { [key]: { _id: { $in: subId } } } }, { returnOriginal: false }, ); return res.value; @@ -214,8 +215,8 @@ export async function deleteSub( export async function getSub>( domainId: string, docType: T, docId: DocType[T]['docId'], - key: K, subId: ObjectID, -): Promise<[DocType[T], DocType[T][K]][0]> { + key: K, subId: DocType[T][K][0]['_id'], +): Promise<[DocType[T], DocType[T][K][0]]> { const doc = await coll.findOne({ domainId, docType, diff --git a/packages/hydrooj/src/model/problem.ts b/packages/hydrooj/src/model/problem.ts index f83590b10..edfc86e28 100644 --- a/packages/hydrooj/src/model/problem.ts +++ b/packages/hydrooj/src/model/problem.ts @@ -1,6 +1,6 @@ import type { Readable } from 'stream'; import { ObjectID, FilterQuery } from 'mongodb'; -import { Dictionary } from 'lodash'; +import { Dictionary, pick } from 'lodash'; import { STATUS } from './builtin'; import * as document from './document'; import * as domain from './domain'; @@ -126,7 +126,7 @@ export function push>(domainId: string, _id: number, k } export function pull>(domainId: string, pid: number, key: ArrayKeys, values: Pdoc[T][0][]) { - return document.pull(domainId, document.TYPE_PROBLEM, pid, key, values.map((_id) => ({ _id }))); + return document.deleteSub(domainId, document.TYPE_PROBLEM, pid, key, values); } export function inc(domainId: string, _id: number, field: NumberKeys, n: number): Promise { @@ -147,29 +147,35 @@ export function del(domainId: string, docId: number) { } export async function addTestdata(domainId: string, pid: number, name: string, f: Readable | Buffer | string) { - await storage.put(`problem/${domainId}/${pid}/testdata/${name}`, f); + const [current] = await Promise.all([ + document.getSub(domainId, document.TYPE_PROBLEM, pid, 'data', name), + storage.put(`problem/${domainId}/${pid}/testdata/${name}`, f), + ]); const meta = await storage.getMeta(`problem/${domainId}/${pid}/testdata/${name}`); - return await push(domainId, pid, 'data', { - _id: name, name, size: meta.size, lastModified: meta.lastModified, etag: meta.etag, - }); + const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) }; + if (!current) await push(domainId, pid, 'data', { _id: name, ...payload }); + else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'data', name, payload); } export async function delTestdata(domainId: string, pid: number, name: string | string[]) { - const names = name instanceof Array ? name : [name]; + const names = (name instanceof Array) ? name : [name]; await storage.del(names.map((t) => `problem/${domainId}/${pid}/testdata/${t}`)); await pull(domainId, pid, 'data', names); } export async function addAdditionalFile(domainId: string, pid: number, name: string, f: Readable | Buffer | string) { - await storage.put(`problem/${domainId}/${pid}/additional_file/${name}`, f); + const [[, fileinfo]] = await Promise.all([ + document.getSub(domainId, document.TYPE_PROBLEM, pid, 'additional_file', name), + storage.put(`problem/${domainId}/${pid}/additional_file/${name}`, f), + ]); const meta = await storage.getMeta(`problem/${domainId}/${pid}/additional_file/${name}`); - return await push(domainId, pid, 'additional_file', { - _id: name, name, size: meta.size, lastModified: meta.lastModified, etag: meta.etag, - }); + const payload = { name, ...pick(meta, ['size', 'lastModified', 'etag']) }; + if (!fileinfo) await push(domainId, pid, 'additional_file', { _id: name, ...payload }); + else await document.setSub(domainId, document.TYPE_PROBLEM, pid, 'additional_file', name, payload); } export async function delAdditionalFile(domainId: string, pid: number, name: string | string[]) { - const names = name instanceof Array ? name : [name]; + const names = (name instanceof Array) ? name : [name]; await storage.del(names.map((t) => `problem/${domainId}/${pid}/additional_file/${t}`)); await pull(domainId, pid, 'additional_file', names); }