From 89fb04bfb3b15814cd1faa36fe27615e52b64a95 Mon Sep 17 00:00:00 2001 From: undefined Date: Mon, 28 Nov 2022 17:33:28 +0800 Subject: [PATCH] core: user-import: enhanced --- packages/hydrooj/locales/en.yaml | 2 ++ packages/hydrooj/package.json | 2 +- packages/hydrooj/src/handler/contest.ts | 8 ++--- packages/hydrooj/src/handler/manage.ts | 40 +++++++++++++++++-------- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/packages/hydrooj/locales/en.yaml b/packages/hydrooj/locales/en.yaml index 7d674f013..f8aaea602 100644 --- a/packages/hydrooj/locales/en.yaml +++ b/packages/hydrooj/locales/en.yaml @@ -57,8 +57,10 @@ pager_previous: ‹ Previous perm_contest: Contests perm_discussion: Discussions perm_general: General +perm_homework: Homework perm_problem_solution: Problem Solutions perm_problem: Problems +perm_ranking: Ranking perm_record: Records perm_training: Trainings problem_create: Problem Create diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index c80fa919a..d1172c3ee 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "4.4.1", + "version": "4.4.2", "bin": "bin/hydrooj.js", "main": "src/plugin-api", "module": "src/plugin-api", diff --git a/packages/hydrooj/src/handler/contest.ts b/packages/hydrooj/src/handler/contest.ts index b3e82bc6a..73d4f9b9d 100644 --- a/packages/hydrooj/src/handler/contest.ts +++ b/packages/hydrooj/src/handler/contest.ts @@ -232,6 +232,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { @param('tid', Types.ObjectID) @param('ext', Types.Range(['csv', 'html', 'ghost']), true) async get(domainId: string, tid: ObjectID, ext = '') { + if (!contest.canShowScoreboard.call(this, this.tdoc, true)) throw new ContestScoreboardHiddenError(tid); if (ext) { await this.exportScoreboard(domainId, tid, ext); return; @@ -248,8 +249,7 @@ export class ContestScoreboardHandler extends ContestDetailBaseHandler { } async exportGhost(domainId: string, tid: ObjectID) { - const tdoc = await contest.get(domainId, tid); - if (!contest.canShowScoreboard.call(this, tdoc)) throw new ContestScoreboardHiddenError(tid); + const tdoc = this.tdoc; const [pdict, teams] = await Promise.all([ problem.getList(domainId, tdoc.pids, true, false, undefined, true), contest.getMultiStatus(domainId, { docId: tid }).toArray(), @@ -538,8 +538,8 @@ export async function apply(ctx) { ctx.Route('contest_detail', '/contest/:tid', ContestDetailHandler, PERM.PERM_VIEW_CONTEST); ctx.Route('contest_broadcast', '/contest/:tid/broadcast', ContestBroadcastHandler); ctx.Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST); - ctx.Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST); - ctx.Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST); + ctx.Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD); + ctx.Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD); ctx.Route('contest_code', '/contest/:tid/code', ContestCodeHandler, PERM.PERM_VIEW_CONTEST); ctx.Route('contest_files', '/contest/:tid/file', ContestFilesHandler, PERM.PERM_VIEW_CONTEST); ctx.Route('contest_file_download', '/contest/:tid/file/:filename', ContestFileDownloadHandler, PERM.PERM_VIEW_CONTEST); diff --git a/packages/hydrooj/src/handler/manage.ts b/packages/hydrooj/src/handler/manage.ts index 18e226267..c4847ee17 100644 --- a/packages/hydrooj/src/handler/manage.ts +++ b/packages/hydrooj/src/handler/manage.ts @@ -209,6 +209,7 @@ class SystemConfigHandler extends SystemHandler { } } +/* eslint-disable no-await-in-loop */ class SystemUserImportHandler extends SystemHandler { async get() { this.response.body.users = []; @@ -219,49 +220,62 @@ class SystemUserImportHandler extends SystemHandler { @param('draft', Types.Boolean) async post(domainId: string, _users: string, draft: boolean) { const users = _users.split('\n'); - const udocs = []; + const udocs: { email: string, username: string, password: string, displayName?: string, payload?: any }[] = []; const messages = []; + const mapping = {}; + const groups: Record = {}; for (const i in users) { const u = users[i]; if (!u.trim()) continue; - let [email, username, password, displayName] = u.split(',').map((t) => t.trim()); - if (!email || !username || !password) [email, username, password, displayName] = u.split('\t').map((t) => t.trim()); + let [email, username, password, displayName, extra] = u.split(',').map((t) => t.trim()); + if (!email || !username || !password) [email, username, password, displayName, extra] = u.split('\t').map((t) => t.trim()); if (email && username && password) { if (!isEmail(email)) messages.push(`Line ${+i + 1}: Invalid email.`); else if (!Types.Username[1](username)) messages.push(`Line ${+i + 1}: Invalid username`); else if (!isPassword(password)) messages.push(`Line ${+i + 1}: Invalid password`); - // eslint-disable-next-line no-await-in-loop else if (await user.getByEmail('system', email)) { messages.push(`Line ${+i + 1}: Email ${email} already exists.`); - // eslint-disable-next-line no-await-in-loop } else if (await user.getByUname('system', username)) { messages.push(`Line ${+i + 1}: Username ${username} already exists.`); } else { + const payload: any = {}; + try { + const data = JSON.parse(extra); + if (data.group) { + if (!groups[data.group]) groups[data.group] = []; + groups[data.group].push(email); + } + if (data.school) payload.school = data.school; + } catch (e) { } udocs.push({ - email, username, password, displayName, + email, username, password, displayName, payload, }); } } else messages.push(`Line ${+i + 1}: Input invalid.`); } messages.push(`${udocs.length} users found.`); if (!draft) { - for (const { - email, username, password, displayName, - } of udocs) { + for (const udoc of udocs) { try { - // eslint-disable-next-line no-await-in-loop - const uid = await user.create(email, username, password); - // eslint-disable-next-line no-await-in-loop - if (displayName) await domain.setUserInDomain(domainId, uid, { displayName }); + const uid = await user.create(udoc.email, udoc.username, udoc.password); + mapping[udoc.email] = uid; + if (udoc.displayName) await domain.setUserInDomain(domainId, uid, { displayName: udoc.displayName }); + if (udoc.payload?.school) await user.setById(uid, { school: udoc.payload.school }); + if (udoc.payload?.studentId) await user.setById(uid, { studentId: udoc.payload.studentId }); } catch (e) { messages.push(e.message); } } } + for (const name in groups) { + const uids = groups[name].map((i) => mapping[i]).filter((i) => i); + if (uids.length) await user.updateGroup(domainId, name, uids); + } this.response.body.users = udocs; this.response.body.messages = messages; } } +/* eslint-enable no-await-in-loop */ const Priv = Object.fromEntries(Object.entries(PRIV).filter( (i) => (!i[0].endsWith('SELF') && !['PRIV_DEFAULT', 'PRIV_NEVER', 'PRIV_NONE', 'PRIV_ALL'].includes(i[0])),