core: feat: problem group-assign

This commit is contained in:
undefined committed 2022-01-07 21:37:19 +08:00
1 parent f699d67019
commit 8fd23e6cea
21 files changed
+250 -104

No files matched your search

+19 -5
View File
@@ -3,7 +3,7 @@ import moment from 'moment-timezone';
import { ObjectID } from 'mongodb';
import { Time } from '@hydrooj/utils/lib/utils';
import {
ContestNotFoundError, ContestNotLiveError, InvalidTokenError,
ContestNotFoundError, ContestNotLiveError, ForbiddenError, InvalidTokenError,
PermissionError, ValidationError,
} from '../error';
import { Tdoc } from '../interface';
@@ -74,6 +74,18 @@ export class ContestListHandler extends Handler {
}
export class ContestDetailHandler extends Handler {
@param('tid', Types.ObjectID)
@param('page', Types.PositiveInt, true)
async prepare(domainId: string, tid: ObjectID) {
const tdoc = await contest.get(domainId, tid);
if (tdoc.assign) {
const groups = await user.listGroup(domainId, this.user._id);
if (!Set.intersection(tdoc.assign, groups.map((i) => i.name)).size) {
throw new ForbiddenError('You are not assigned.');
}
}
}
@param('tid', Types.ObjectID)
@param('page', Types.PositiveInt, true)
async get(domainId: string, tid: ObjectID, page = 1) {
@@ -238,9 +250,11 @@ export class ContestEditHandler extends Handler {
@param('rated', Types.Boolean)
@param('code', Types.String, true)
@param('autoHide', Types.String, true)
@param('assign', Types.Array, true)
async post(
domainId: string, tid: ObjectID, beginAtDate: string, beginAtTime: string, duration: number,
title: string, content: string, rule: string, _pids: string, rated = false, _code = '', autoHide = false,
title: string, content: string, rule: string, _pids: string, rated = false,
_code = '', autoHide = false, assign: string[] = null,
) {
if (autoHide) this.checkPerm(PERM.PERM_EDIT_PROBLEM);
const pids = _pids.replace(/,/g, ',').split(',').map((i) => +i).filter((i) => i);
@@ -249,7 +263,7 @@ export class ContestEditHandler extends Handler {
const endAt = beginAtMoment.clone().add(duration, 'hours').toDate();
if (beginAtMoment.isSameOrAfter(endAt)) throw new ValidationError('duration');
const beginAt = beginAtMoment.toDate();
await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, true);
await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, this.user.group, true);
if (tid) {
await contest.edit(domainId, tid, {
title, content, rule, beginAt, endAt, pids, rated,
@@ -267,13 +281,13 @@ export class ContestEditHandler extends Handler {
await TaskModel.deleteMany(task);
if (Date.now() <= endAt.getTime() && autoHide) {
// eslint-disable-next-line no-await-in-loop
for (const pid of pids) await problem.edit(domainId, pid, { hidden: true });
await Promise.all(pids.map((pid) => problem.edit(domainId, pid, { hidden: true })));
await TaskModel.add({
...task,
executeAfter: endAt,
});
}
await contest.edit(domainId, tid, { _code, autoHide });
await contest.edit(domainId, tid, { assign, _code, autoHide });
this.response.body = { tid };
this.response.redirect = this.url('contest_detail', { tid });
}
+6 -4
View File
@@ -58,7 +58,11 @@ class DiscussionHandler extends Handler {
// TODO(twd2): exclude problem/contest discussions?
// TODO(iceboy): continuation based pagination.
this.vnode = await discussion.getVnode(domainId, typeMapper[type], name, this.user._id);
if (this.vnode.hidden) this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
if (this.vnode.assign?.length) {
if (!Set.intersection(this.vnode.assign, this.user.group).size) {
throw new PermissionError(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
}
if (this.ddoc) {
this.ddoc.parentType = this.ddoc.parentType || this.vnode.type;
this.ddoc.parentId = this.ddoc.parentId || this.vnode.id;
@@ -78,9 +82,7 @@ class DiscussionMainHandler extends Handler {
);
const udict = await user.getList(domainId, ddocs.map((ddoc) => ddoc.owner));
const [vndict, vnodes] = await Promise.all([
discussion.getListVnodes(
domainId, ddocs, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN),
),
discussion.getListVnodes(domainId, ddocs, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), this.user.group),
discussion.getNodes(domainId),
]);
this.response.template = 'discussion_main_or_node.html';
+56 -2
View File
@@ -3,7 +3,7 @@ import { Dictionary } from 'lodash';
import moment from 'moment-timezone';
import {
DomainJoinAlreadyMemberError, DomainJoinForbiddenError, ForbiddenError,
InvalidJoinInvitationCodeError, RoleAlreadyExistError, ValidationError,
InvalidJoinInvitationCodeError, PermissionError, RoleAlreadyExistError, ValidationError,
} from '../error';
import type { DomainDoc } from '../interface';
import avatar from '../lib/avatar';
@@ -19,6 +19,40 @@ import {
Handler, param, post, query, Route, Types,
} from '../service/server';
import { log2 } from '../utils';
import { registerResolver, registerValue } from './api';
registerValue('GroupInfo', [
['name', 'String!'],
['uids', '[Int]!'],
]);
registerResolver('Query', 'domain(id: String)', 'Domain', async (args, ctx) => {
const ddoc = await domain.get(args.id);
if (!ddoc) return null;
const udoc = await user.getById(args.id, ctx.user._id);
if (!udoc.hasPerm(PERM.PERM_VIEW) && !udoc.hasPriv(PRIV.PRIV_VIEW_ALL_DOMAIN)) return null;
ctx.udoc = udoc;
return ddoc;
});
registerResolver('Domain', 'manage', 'DomainManage', async (args, ctx) => {
if (!ctx.udoc.hasPerm(PERM.PERM_EDIT_DOMAIN)) throw new PermissionError(PERM.PERM_EDIT_DOMAIN);
return {};
});
registerResolver('DomainManage', 'group', 'DomainGroup', (args, ctx) => ctx.parent);
registerResolver(
'DomainGroup', 'list(uid: Int)', '[GroupInfo]',
(args, ctx) => user.listGroup(ctx.parent._id, args.uid),
);
registerResolver(
'DomainGroup', 'update(name: String!, uids: [Int]!)', 'Boolean',
async (args, ctx) => !!(await user.updateGroup(ctx.parent._id, args.name, args.uids)).upsertedCount,
);
registerResolver(
'DomainGroup', 'del(name: String!)', 'Boolean',
async (args, ctx) => !!(await user.delGroup(ctx.parent._id, args.name)).deletedCount,
);
class DomainRankHandler extends Handler {
@query('page', Types.PositiveInt, true)
@@ -184,7 +218,7 @@ class DomainRoleHandler extends ManageHandler {
@param('roles', Types.Array)
async postDelete(domainId: string, roles: string[]) {
if (Set.intersection(new Set(roles), new Set(['root', 'default', 'guest'])).size > 0) {
if (Set.intersection(roles, ['root', 'default', 'guest']).size > 0) {
throw new ValidationError('role', null, 'You cannot delete root, default or guest roles');
}
await domain.deleteRoles(domainId, roles);
@@ -231,6 +265,25 @@ class DomainJoinApplicationsHandler extends ManageHandler {
}
}
class DomainUserGroupHandler extends ManageHandler {
async get() {
this.response.body = 'domain_group.html';
}
@param('name', Types.Name)
async postDel(domainId: string, name: string) {
await user.delGroup(domainId, name);
this.back();
}
@param('name', Types.Name)
@param('uids', Types.NumericArray)
async postUpdate(domainId: string, name: string, uids: number[]) {
await user.updateGroup(domainId, name, uids);
this.back();
}
}
class DomainJoinHandler extends Handler {
joinSettings: any;
noCheckPermView = true;
@@ -283,6 +336,7 @@ export async function apply() {
Route('domain_user', '/domain/user', DomainUserHandler);
Route('domain_permission', '/domain/permission', DomainPermissionHandler);
Route('domain_role', '/domain/role', DomainRoleHandler);
Route('domain_group', '/domain/group', DomainUserGroupHandler);
Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler);
Route('domain_join', '/domain/join', DomainJoinHandler, PRIV.PRIV_USER_PROFILE);
Route('domain_search', '/domain/search', DomainSearchHandler, PRIV.PRIV_USER_PROFILE);
+2 -4
View File
@@ -75,9 +75,7 @@ class HomeHandler extends Handler {
async getDiscussion(domainId: string, limit = 20) {
if (this.user.hasPerm(PERM.PERM_VIEW_DISCUSSION)) {
const ddocs = await discussion.getMulti(domainId).limit(limit).toArray();
const vndict = await discussion.getListVnodes(
domainId, ddocs, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN),
);
const vndict = await discussion.getListVnodes(domainId, ddocs, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN), this.user.group);
this.collectUser(ddocs.map((ddoc) => ddoc.owner));
return ['discussion', ddocs, vndict];
}
@@ -103,7 +101,7 @@ class HomeHandler extends Handler {
for (const psdoc of psdocs) psdict[psdoc.docId] = psdoc;
const pdict = await ProblemModel.getList(
domainId, psdocs.map((pdoc) => pdoc.docId),
this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, false,
this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, this.user.group, false,
);
const pdocs = Object.keys(pdict).filter((i) => +i).map((i) => pdict[i]);
return ['problem', pdocs, psdict];
+14 -2
View File
@@ -4,7 +4,8 @@ import moment from 'moment-timezone';
import { ObjectID } from 'mongodb';
import { Time } from '@hydrooj/utils/lib/utils';
import {
ContestNotFoundError, HomeworkNotLiveError, ValidationError,
ContestNotFoundError, ForbiddenError, HomeworkNotLiveError,
ValidationError,
} from '../error';
import { PenaltyRules } from '../interface';
import paginate from '../lib/paginate';
@@ -40,6 +41,17 @@ class HomeworkMainHandler extends Handler {
}
class HomeworkDetailHandler extends Handler {
@param('tid', Types.ObjectID)
async prepare(domainId: string, tid: ObjectID) {
const tdoc = await contest.get(domainId, tid);
if (tdoc.rule !== 'homework') throw new ContestNotFoundError(domainId, tid);
if (tdoc.assign) {
if (!Set.intersection(tdoc.assign, this.user.group).size) {
throw new ForbiddenError('You are not assigned.');
}
}
}
@param('tid', Types.ObjectID)
@param('page', Types.PositiveInt, true)
async get(domainId: string, tid: ObjectID, page = 1) {
@@ -156,7 +168,7 @@ class HomeworkEditHandler extends Handler {
const endAt = penaltySince.clone().add(extensionDays, 'days');
if (beginAt.isSameOrAfter(penaltySince)) throw new ValidationError('endAtDate', 'endAtTime');
if (penaltySince.isAfter(endAt)) throw new ValidationError('extensionDays');
await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, true);
await problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, this.user.group, true);
if (!tid) {
tid = await contest.add(domainId, title, content, this.user._id,
'homework', beginAt.toDate(), endAt.toDate(), pids, rated,
+52 -24
View File
@@ -1,17 +1,18 @@
import AdmZip from 'adm-zip';
import { statSync } from 'fs-extra';
import { flatten, intersection, isSafeInteger } from 'lodash';
import { intersection, isSafeInteger } from 'lodash';
import { FilterQuery, ObjectID } from 'mongodb';
import { sortFiles } from '@hydrooj/utils/lib/utils';
import {
BadRequestError, ContestNotAttendedError, ContestNotEndedError,
ContestNotFoundError, ContestNotLiveError,
ForbiddenError, NoProblemError, NotFoundError,
PermissionError, ProblemNotFoundError, SolutionNotFoundError, ValidationError,
PermissionError, ProblemNotFoundError, SolutionNotFoundError,
ValidationError,
} from '../error';
import {
ProblemConfig,
ProblemDoc, ProblemStatusDoc, Tdoc, User,
ProblemConfig, ProblemDoc, ProblemStatusDoc,
Tdoc, User,
} from '../interface';
import paginate from '../lib/paginate';
import { isPid, parsePid as convertPid } from '../lib/validator';
@@ -32,7 +33,7 @@ import {
} from '../service/server';
import { registerResolver, registerValue } from './api';
export const parseCategory = (value: string) => flatten(value.replace(/,/g, ',').split(',')).map((e) => e.trim());
export const parseCategory = (value: string) => value.replace(/,/g, ',').split(',').map((e) => e.trim());
export const parsePid = (value: string) => (isSafeInteger(value) ? +value : value);
registerValue('FileInfo', [
@@ -58,9 +59,7 @@ registerValue('Problem', [
['difficulty', 'Int'],
['tag', '[String]'],
['hidden', 'Boolean'],
]);
registerValue('ProblemManage', [
['delete', 'Boolean!'],
['assign', '[String]'],
]);
registerResolver(
@@ -68,7 +67,12 @@ registerResolver(
async (arg, ctx) => {
const pdoc = await problem.get(ctx.domainId, arg.pid || arg.id);
if (!pdoc) return null;
if (pdoc.hidden && !ctx.user.own(pdoc)) ctx.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
if (pdoc.hidden) ctx.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
if (pdoc.assign.length && !ctx.user.own(pdoc)) {
if (!Set.intersection(new Set(pdoc.assign), new Set(ctx.user.group)).size) {
throw new PermissionError(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
}
ctx.pdoc = pdoc;
return pdoc;
},
@@ -89,10 +93,33 @@ registerResolver(
},
);
registerResolver(
'ProblemManage', 'edit(title: String, content: String, tag: [String], hidden: Boolean)', 'Problem!',
'ProblemManage', 'edit(title: String, content: String, tag: [String], hidden: Boolean, assign: [String])', 'Problem!',
(arg, ctx) => problem.edit(ctx.domainId, ctx.pdoc.docId, arg),
);
function buildQuery(udoc: User) {
const q: FilterQuery<ProblemDoc> = {
$or: [
{
$and: [
{
$or: [
{ assign: { $size: 0 } },
{ assign: { $in: udoc.group } },
],
},
],
},
{ owner: udoc._id },
{ maintainer: udoc._id },
],
};
if (udoc.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) {
q.$or[0].$and.push({ hidden: false });
}
return q;
}
export class ProblemHandler extends Handler {
async cleanup() {
if (this.response.template === 'problem_main.html' && this.request.json) {
@@ -123,7 +150,7 @@ export class ProblemMainHandler extends ProblemHandler {
async get(domainId: string, page = 1, q = '', category = []) {
this.response.template = 'problem_main.html';
// eslint-disable-next-line @typescript-eslint/no-shadow
const query: FilterQuery<ProblemDoc> = {};
const query = buildQuery(this.user);
const psdict = {};
const search = global.Hydro.lib.problemSearch;
let sort: string[];
@@ -145,9 +172,6 @@ export class ProblemMainHandler extends ProblemHandler {
sort = result;
} else query.$text = { $search: q };
}
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) {
query.$or = [{ hidden: false }, { owner: this.user._id }, { maintainer: this.user._id }];
}
await bus.serial('problem/list', query, this);
// eslint-disable-next-line prefer-const
let [pdocs, ppcount, pcount] = fail
@@ -160,7 +184,7 @@ export class ProblemMainHandler extends ProblemHandler {
if (sort) pdocs = pdocs.sort((a, b) => sort.indexOf(`${a.domainId}/${a.docId}`) - sort.indexOf(`${b.domainId}/${b.docId}`));
if (q) {
const pdoc = await problem.get(domainId, +q || q, problem.PROJECTION_LIST);
if (pdoc && (!pdoc.hidden || this.user.own(pdoc) || this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN))) {
if (pdoc && problem.canViewBy(pdoc, this.user)) {
const count = pdocs.length;
pdocs = pdocs.filter((doc) => doc.docId !== pdoc.docId);
pdocs.unshift(pdoc);
@@ -252,9 +276,8 @@ export class ProblemMainHandler extends ProblemHandler {
export class ProblemRandomHandler extends ProblemHandler {
@param('category', Types.Name, true, null, parseCategory)
async get(domainId: string, category: string[] = []) {
const q: FilterQuery<ProblemDoc> = category.length ? { $and: [] } : {};
for (const tag of category) q.$and.push({ tag });
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) q.hidden = false;
const q = buildQuery(this.user);
if (category.length) q.$and = category.map((tag) => ({ tag }));
await bus.serial('problem/list', q, this);
const pid = await problem.random(domainId, q);
if (!pid) throw new NoProblemError();
@@ -284,8 +307,8 @@ export class ProblemDetailHandler extends ProblemHandler {
if (!showAccept) this.pdoc.nAccept = 0;
if (contest.isNotStarted(this.tdoc)) throw new ContestNotLiveError(tid);
if (!contest.isDone(this.tdoc) && !this.tsdoc?.attend) throw new ContestNotAttendedError(tid);
} else if (this.pdoc.hidden && !this.user.own(this.pdoc)) {
this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
} else if (!problem.canViewBy(this.pdoc, this.user)) {
throw new PermissionError(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
let ddoc = this.domain;
if (this.pdoc.reference) {
@@ -462,13 +485,14 @@ export class ProblemEditHandler extends ProblemManageHandler {
@post('hidden', Types.Boolean)
@post('tag', Types.Content, true, null, parseCategory)
@post('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
@post('assign', Types.CommaSeperatedArray)
async post(
domainId: string, pid: string | number, title: string, content: string,
newPid: string = '', hidden = false, tag: string[] = [], difficulty = 0,
newPid: string = '', hidden = false, tag: string[] = [], difficulty = 0, assign: string[] = [],
) {
if (newPid !== this.pdoc.pid && await problem.get(domainId, newPid)) throw new BadRequestError('new pid exists');
const $update: Partial<ProblemDoc> = {
title, content, pid: newPid, hidden, tag: tag ?? [], difficulty,
title, content, pid: newPid, hidden, assign, tag: tag ?? [], difficulty,
};
let pdoc = await problem.get(domainId, pid);
pdoc = await problem.edit(domainId, pdoc.docId, $update);
@@ -765,9 +789,13 @@ export class ProblemCreateHandler extends Handler {
@post('hidden', Types.Boolean)
@post('difficulty', Types.PositiveInt, (i) => +i <= 10, true)
@post('tag', Types.Content, true, null, parseCategory)
async post(domainId: string, title: string, content: string, pid: string, hidden = false, difficulty = 0, tag: string[] = []) {
@post('assign', Types.CommaSeperatedArray)
async post(
domainId: string, title: string, content: string, pid: string,
hidden = false, difficulty = 0, tag: string[] = [], assign: string[] = [],
) {
if (pid && await problem.get(domainId, pid)) throw new BadRequestError('invalid pid');
const docId = await problem.add(domainId, pid, title, content, this.user._id, tag ?? [], hidden);
const docId = await problem.add(domainId, pid, title, content, this.user._id, tag ?? [], hidden, assign);
if (difficulty) await problem.edit(domainId, docId, { difficulty });
this.response.body = { pid: pid || docId };
this.response.redirect = this.url('problem_files', { pid: pid || docId });
+10 -13
View File
@@ -69,13 +69,13 @@ class RecordListHandler extends Handler {
const rdocs = invalid
? [] as RecordDoc[]
: await cursor.skip((page - 1) * limit).limit(limit).toArray();
const canViewProblem = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM);
const canViewProblemHidden = (!!tid) || this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
const canViewProblem = tid || this.user.hasPerm(PERM.PERM_VIEW_PROBLEM);
const canViewHiddenProblem = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id;
const [udict, pdict] = full ? [{}, {}]
: await Promise.all([
user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)),
canViewProblem
? problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), canViewProblemHidden || this.user._id, false)
? problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), canViewHiddenProblem, this.user.group, false)
: Object.fromEntries([rdocs.map((rdoc) => [rdoc.pid, { ...problem.default, pid: rdoc.pid }])]),
]);
this.response.body = {
@@ -132,10 +132,9 @@ class RecordDetailHandler extends Handler {
rdoc.compilerTexts = [];
}
let canView = pdoc && this.user.own(pdoc);
canView ||= !pdoc?.hidden && this.user.hasPerm(PERM.PERM_VIEW_PROBLEM);
canView ||= !!rdoc.contest || this.user._id === rdoc.uid;
if (!canView) this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
if (pdoc && !(rdoc.contest && this.user._id === rdoc.uid)) {
if (!problem.canViewBy(pdoc, this.user)) throw new PermissionError(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
this.response.body = {
udoc, rdoc, pdoc, tdoc,
@@ -250,7 +249,7 @@ class RecordMainConnectionHandler extends ConnectionHandler {
]);
const tdoc = this.tid ? this.tdoc || await contest.get(rdoc.domainId, new ObjectID(this.tid)) : null;
if (pdoc && !rdoc.contest) {
if (pdoc.hidden && !this.user.own(pdoc) && !this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN)) pdoc = null;
if (problem.canViewBy(pdoc, this.user)) pdoc = null;
if (!this.user.hasPerm(PERM.PERM_VIEW_PROBLEM)) pdoc = null;
}
if (this.pretest) this.send({ rdoc });
@@ -293,11 +292,9 @@ class RecordDetailConnectionHandler extends ConnectionHandler {
rdoc.compilerTexts = [];
}
let canView = pdoc && this.user.own(pdoc);
canView ||= !pdoc?.hidden && this.user.hasPerm(PERM.PERM_VIEW_PROBLEM);
canView ||= this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
canView ||= !!rdoc.contest || this.user._id === rdoc.uid;
if (!canView) throw new PermissionError(PERM.PERM_READ_RECORD_CODE);
if (!(rdoc.contest && this.user._id === rdoc.uid)) {
if (!problem.canViewBy(pdoc, this.user)) throw new PermissionError(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
this.rid = rid.toString();
this.cleanup = bus.on('record/change', this.onRecordChange.bind(this));
+3 -2
View File
@@ -97,9 +97,10 @@ class TrainingDetailHandler extends Handler {
const tdoc = await training.get(domainId, tid);
await bus.serial('training/get', tdoc, this);
const pids = training.getPids(tdoc.dag);
const canViewHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id;
const [owner, pdict] = await Promise.all([
user.getById(domainId, tdoc.owner),
problem.getList(domainId, pids, this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id, true),
problem.getList(domainId, pids, canViewHidden, this.user.group, true),
]);
const psdict = await problem.getListStatus(domainId, this.user._id, pids);
const donePids = new Set<number>();
@@ -119,7 +120,7 @@ class TrainingDetailHandler extends Handler {
for (const node of tdoc.dag) {
ndict[node._id] = node;
const totalCount = node.pids.length;
const doneCount = Set.intersection(new Set(node.pids), donePids).size;
const doneCount = Set.intersection(node.pids, donePids).size;
const nsdoc = {
progress: totalCount ? Math.floor(100 * (doneCount / totalCount)) : 100,
isDone: training.isDone(node, doneNids, donePids),
+6 -1
View File
@@ -303,7 +303,12 @@ class UserDetailHandler extends Handler {
const canViewHidden = this.user.hasPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN) || this.user._id;
await Promise.all([domainId, ...(union?.union || [])].map(async (did) => {
const psdocs = await problem.getMultiStatus(did, { uid, status: STATUS.STATUS_ACCEPTED }).toArray();
pdocs.push(...Object.values(await problem.getList(did, psdocs.map((i) => i.docId), canViewHidden, false, undefined, true)));
pdocs.push(...Object.values(
await problem.getList(
did, psdocs.map((i) => i.docId), canViewHidden,
this.user.group, false, undefined, true,
),
));
}));
for (const pdoc of pdocs) {
for (const tag of pdoc.tag) {