core&ui: add clarification (#669)

This commit is contained in:
panda authored and GitHub committed 2023-11-05 06:49:51 +08:00
1 parent bc4f5115d2
commit c8c8063240
14 files changed
+373 -80

No files matched your search

+46 -7
View File
@@ -256,12 +256,13 @@ export class ContestProblemListHandler extends ContestDetailBaseHandler {
async get(domainId: string, tid: ObjectId) {
if (contest.isNotStarted(this.tdoc)) throw new ContestNotLiveError(domainId, tid);
if (!this.tsdoc?.attend && !contest.isDone(this.tdoc)) throw new ContestNotAttendedError(domainId, tid);
const [pdict, udict] = await Promise.all([
const [pdict, udict, tcdocs] = await Promise.all([
problem.getList(domainId, this.tdoc.pids, true, true, problem.PROJECTION_CONTEST_LIST),
user.getList(domainId, [this.tdoc.owner, this.user._id]),
contest.getMultiClarification(domainId, tid, this.user._id),
]);
this.response.body = {
pdict, psdict: {}, udict, rdict: {}, tdoc: this.tdoc, tsdoc: this.tsdoc,
pdict, psdict: {}, udict, rdict: {}, tdoc: this.tdoc, tsdoc: this.tsdoc, tcdocs,
};
this.response.template = 'contest_problemlist.html';
if (!this.tsdoc) return;
@@ -282,6 +283,24 @@ export class ContestProblemListHandler extends ContestDetailBaseHandler {
for (const i of psdocs) this.response.body.rdict[i.rid] = { _id: i.rid };
}
}
@param('tid', Types.ObjectId)
@param('content', Types.Content)
@param('subject', Types.Int)
async postClarification(domainId: string, tid: ObjectId, content: string, subject: number) {
if (!this.tsdoc?.attend) throw new ContestNotAttendedError(domainId, tid);
if (!contest.isOngoing(this.tdoc)) throw new ContestNotLiveError(domainId, tid);
await this.limitRate('add_discussion', 3600, 60);
await contest.addClarification(domainId, tid, this.user._id, content, this.request.ip, subject);
if (!this.user.own(this.tdoc)) {
await Promise.all([this.tdoc.owner, ...this.tdoc.maintainer].map((uid) => message.send(1, uid, JSON.stringify({
message: 'Contest {0} has a new clarification about {1}, please go to contest management to reply.',
params: [this.tdoc.title, subject > 0 ? `#${this.tdoc.pids.indexOf(subject) + 1}` : 'the contest'],
url: this.url('contest_manage', { tid }),
}), message.FLAG_I18N | message.FLAG_UNREAD)));
}
this.back();
}
}
export class ContestScoreboardHandler extends ContestDetailBaseHandler {
@@ -558,12 +577,15 @@ export class ContestCodeHandler extends Handler {
export class ContestManagementHandler extends ContestManagementBaseHandler {
@param('tid', Types.ObjectId)
async get(domainId: string, tid: ObjectId) {
const tcdocs = await contest.getMultiClarification(domainId, tid);
this.response.body = {
tdoc: this.tdoc,
tsdoc: this.tsdoc,
owner_udoc: await user.getById(domainId, this.tdoc.owner),
pdict: await problem.getList(domainId, this.tdoc.pids, true, true, problem.PROJECTION_CONTEST_LIST),
files: sortFiles(this.tdoc.files || []),
udict: await user.getListForRender(domainId, tcdocs.map((i) => i.owner)),
tcdocs,
urlForFile: (filename: string) => this.url('contest_file_download', { tid, filename }),
};
this.response.pjax = 'partials/files.html';
@@ -572,11 +594,28 @@ export class ContestManagementHandler extends ContestManagementBaseHandler {
@param('tid', Types.ObjectId)
@param('content', Types.Content)
async postBroadcast(domainId: string, tid: ObjectId, content: string) {
const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray();
const uids = Array.from<number>(new Set(tsdocs.map((tsdoc) => tsdoc.uid)));
const flag = contest.isOngoing(this.tdoc) ? message.FLAG_ALERT : message.FLAG_UNREAD;
await Promise.all(uids.map((uid) => message.send(this.user._id, uid, content, flag)));
@param('did', Types.ObjectId, true)
@param('subject', Types.Int, true)
async postClarification(domainId: string, tid: ObjectId, content: string, did: ObjectId, subject = 0) {
if (did) {
const tcdoc = await contest.getClarification(domainId, did);
await Promise.all([
contest.addClarificationReply(domainId, did, 0, content, this.request.ip),
message.send(1, tcdoc.owner, JSON.stringify({
message: 'Contest {0} jury replied to your clarification, please go to contest page to view.',
params: [this.tdoc.title],
url: this.url('contest_problemlist', { tid }),
}), message.FLAG_I18N | message.FLAG_ALERT),
]);
} else {
const tsdocs = await contest.getMultiStatus(domainId, { docId: tid }).toArray();
const uids = Array.from<number>(new Set(tsdocs.map((tsdoc) => tsdoc.uid)));
const flag = contest.isOngoing(this.tdoc) ? message.FLAG_ALERT : message.FLAG_UNREAD;
await Promise.all([
contest.addClarification(domainId, tid, 0, content, this.request.ip, subject),
...uids.map((uid) => message.send(1, uid, content, flag)),
]);
}
this.back();
}
+1 -1
View File
@@ -267,7 +267,7 @@ export class ProblemMainHandler extends Handler {
// eslint-disable-next-line no-await-in-loop
await problem.del(domainId, pid);
i++;
this.progress(`Deleting: (${i}/${pids.length})`);
this.progress('Deleting: ({0}/{1})', [i, pids.length]);
}
this.back();
}
+12
View File
@@ -493,6 +493,18 @@ export interface DiscussionTailReplyDoc {
editor?: number;
}
export interface ContestClarificationDoc extends Document {
docType: document['TYPE_CONTEST_CLARIFICATION'];
docId: ObjectId;
parentType: document['TYPE_CONTEST'];
parentId: ObjectId;
// 0: contest -1: technique [pid]: problem
subject: number;
ip: string;
content: string;
reply: DiscussionTailReplyDoc[];
}
export interface TokenDoc {
_id: string,
tokenType: number,
+35
View File
@@ -911,6 +911,37 @@ export async function getScoreboard(
return [tdoc, rows, udict, pdict];
}
export function addClarification(
domainId: string, tid: ObjectId, owner: number, content: string,
ip: string, subject = 0,
) {
return document.add(
domainId, content, owner, document.TYPE_CONTEST_CLARIFICATION,
null, document.TYPE_CONTEST, tid, { ip, subject },
);
}
export function addClarificationReply(
domainId: string, did: ObjectId, owner: number,
content: string, ip: string,
) {
return document.push(
domainId, document.TYPE_CONTEST_CLARIFICATION, did,
'reply', { content, owner, ip },
);
}
export function getClarification(domainId: string, did: ObjectId) {
return document.get(domainId, document.TYPE_CONTEST_CLARIFICATION, did);
}
export function getMultiClarification(domainId: string, tid: ObjectId, owner = 0) {
return document.getMulti(
domainId, document.TYPE_CONTEST_CLARIFICATION,
{ parentType: document.TYPE_CONTEST, parentId: tid, ...(owner ? { owner: { $in: [owner, 0] } } : {}) },
).sort('_id', -1).toArray();
}
export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
isNew(tdoc)
? 'New'
@@ -948,6 +979,10 @@ global.Hydro.model.contest = {
canShowScoreboard,
canViewHiddenScoreboard,
getScoreboard,
addClarification,
addClarificationReply,
getClarification,
getMultiClarification,
isNew,
isUpcoming,
isNotStarted,
+4 -1
View File
@@ -5,7 +5,7 @@ import {
} from 'mongodb';
import { Context } from '../context';
import {
Content, DiscussionDoc,
Content, ContestClarificationDoc, DiscussionDoc,
DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc,
Tdoc, TrainingDoc,
} from '../interface';
@@ -27,6 +27,7 @@ export const TYPE_DISCUSSION_NODE: 20 = 20;
export const TYPE_DISCUSSION: 21 = 21;
export const TYPE_DISCUSSION_REPLY: 22 = 22;
export const TYPE_CONTEST: 30 = 30;
export const TYPE_CONTEST_CLARIFICATION: 31 = 31;
export const TYPE_TRAINING: 40 = 40;
/** @deprecated use `TYPE_CONTEST` with rule `homework` instead. */
export const TYPE_HOMEWORK: 60 = 60;
@@ -39,6 +40,7 @@ export interface DocType {
[TYPE_DISCUSSION]: DiscussionDoc;
[TYPE_DISCUSSION_REPLY]: DiscussionReplyDoc;
[TYPE_CONTEST]: Tdoc;
[TYPE_CONTEST_CLARIFICATION]: ContestClarificationDoc;
[TYPE_TRAINING]: TrainingDoc;
}
@@ -498,6 +500,7 @@ global.Hydro.model.document = {
setSub,
TYPE_CONTEST,
TYPE_CONTEST_CLARIFICATION,
TYPE_DISCUSSION,
TYPE_DISCUSSION_NODE,
TYPE_DISCUSSION_REPLY,
+2 -1
View File
@@ -12,6 +12,7 @@ class MessageModel {
static FLAG_ALERT = 2;
static FLAG_RICHTEXT = 4;
static FLAG_INFO = 8;
static FLAG_I18N = 16;
static coll = db.collection('message');
@@ -33,7 +34,7 @@ class MessageModel {
static async sendInfo(to: number, content: string) {
const _id = new ObjectId();
const mdoc: MessageDoc = {
_id, from: 1, to, content, flag: MessageModel.FLAG_INFO,
_id, from: 1, to, content, flag: MessageModel.FLAG_INFO | MessageModel.FLAG_I18N,
};
bus.broadcast('user/message', to, mdoc);
}
+2 -2
View File
@@ -183,8 +183,8 @@ export class Handler extends HandlerCommon {
}
// This is beta API, may be changed in the future.
progress(message: string) {
Hydro.model.message.sendInfo(this.user._id, message);
progress(message: string, params: any[]) {
Hydro.model.message.sendInfo(this.user._id, JSON.stringify({ message, params }));
}
async init() {