Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions packages/codemate-plugin/plugins/tags-entity/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Context, Handler, ObjectId, param, PRIV, Types } from 'hydrooj';
import TagModel from './model';

class TagManageBaseHandler extends Handler {
async _prepare() {
this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
}
}

class TagAddHandler extends TagManageBaseHandler {
@param('name', Types.String)
@param('alias', Types.ArrayOf<Types['String']>)
@param('description', Types.String)
async post(domainId: string, _name: string, alias: string[], _description: string) {
const name = JSON.parse(_name);
const description = JSON.parse(_description);
const docId = await TagModel.add(domainId, name, alias, description);
this.response.body = { docId };
}
}

class TagEditHandler extends TagManageBaseHandler {
@param('docId', Types.ObjectId)
@param('name', Types.String)
@param('alias', Types.ArrayOf<Types['String']>)
@param('description', Types.String)
async post(domainId: string, docId: ObjectId, _name: string, alias: string[], _description: string) {
const name = JSON.parse(_name);
const description = JSON.parse(_description);
await TagModel.edit(domainId, docId, name, alias, description);
this.response.body = { docId };
}
}

class TagMainHandler extends Handler {
async get({ domainId }) {
const tags = await TagModel.getMulti(domainId);
this.response.body = { tags };
}
}

class TagDeleteHandler extends TagManageBaseHandler {
@param('docId', Types.ObjectId)
async get(domainId: string, docId: ObjectId) {
await TagModel.del(domainId, docId);
this.response.body = { success: true };
}
}

class TagGetHandler extends Handler {
@param('docId', Types.String)
async get(domainId: string, docId: ObjectId) {
this.response.body = { tagDoc: await TagModel.get(domainId, docId) };
}
}

export async function apply(ctx: Context) {
ctx.Route('tag_add', '/tag/add', TagAddHandler);
ctx.Route('tag_edit', '/tag/edit', TagEditHandler);
ctx.Route('tag_main', '/tag/list', TagMainHandler);
ctx.Route('tag_delete', '/tag/delete', TagDeleteHandler);
ctx.Route('tag_get', '/tag/get', TagGetHandler);
}
39 changes: 39 additions & 0 deletions packages/codemate-plugin/plugins/tags-entity/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Document, DocumentModel, ObjectId, Projection } from 'hydrooj';

type stringDict = { [p: string]: string };

export interface TagDoc extends Document {
name: stringDict;
alias: string[];
description: stringDict;
}

export default class TagModel {
static async add(domainId: string, name: stringDict, alias: string[], description: stringDict): Promise<ObjectId> {
return await DocumentModel.add(domainId, '', 1, DocumentModel.TYPE_TAGS, null, null, null, {
name,
alias,
description,
});
}

static async del(domainId: string, docId: ObjectId) {
await DocumentModel.deleteOne(domainId, DocumentModel.TYPE_TAGS, docId);
}

static async edit(domainId: string, docId: ObjectId, name: stringDict, alias: string[], description: stringDict) {
await DocumentModel.set(domainId, DocumentModel.TYPE_TAGS, docId, {
name,
alias,
description,
});
}

static async get(domainId: string, docId: ObjectId) {
return await DocumentModel.get(domainId, DocumentModel.TYPE_TAGS, docId);
}

static async getMulti(domainId: string, query?: Partial<TagDoc>, projection?: Projection<TagDoc>) {
return DocumentModel.getMulti(domainId, DocumentModel.TYPE_TAGS, query, projection);
}
}
30 changes: 21 additions & 9 deletions packages/hydrooj/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,15 @@ export interface Udoc extends Record<string, any> {

/**
* export const enum UserRole {
PRIMARY_SCHOOL_STUDENT = 0, // 小学生
JUNIOR_MIDDLE_SCHOOL_STUDENT = 1, // 初中生
SENIOR_MIDDLE_SCHOOL_STUDENT = 2, // 高中生
ADULT = 10, // 一般成人
COLLEGE_STUDENT = 11, // 大学生
SCHOOL_TEACHER = 12, // 中小学教师
INSTITUTE_TEACHER = 13, // 机构教师
STUDENT_PARENT = 14, // 学生家长
}
PRIMARY_SCHOOL_STUDENT = 0, // 小学生
JUNIOR_MIDDLE_SCHOOL_STUDENT = 1, // 初中生
SENIOR_MIDDLE_SCHOOL_STUDENT = 2, // 高中生
ADULT = 10, // 一般成人
COLLEGE_STUDENT = 11, // 大学生
SCHOOL_TEACHER = 12, // 中小学教师
INSTITUTE_TEACHER = 13, // 机构教师
STUDENT_PARENT = 14, // 学生家长
}
*/

export interface VUdoc {
Expand Down Expand Up @@ -156,6 +156,7 @@ export interface BaseUser {
displayName?: string;
studentId?: string;
}

export type BaseUserDict = Record<number, BaseUser>;

export interface FileInfo {
Expand Down Expand Up @@ -244,18 +245,21 @@ export interface PlainContentNode {
subType: 'html' | 'markdown';
text: string;
}

export interface TextContentNode {
type: 'Text';
subType: 'html' | 'markdown';
sectionTitle: string;
text: string;
}

export interface SampleContentNode {
type: 'Sample';
text: string;
sectionTitle: string;
payload: [string, string];
}

// TODO drop contentNode support
export type ContentNode = PlainContentNode | TextContentNode | SampleContentNode;
export type Content = string | ContentNode[] | Record<string, ContentNode[]>;
Expand All @@ -279,7 +283,9 @@ declare module './model/problem' {
content: string;
nSubmit: number;
nAccept: number;
/** @deprecated */
tag: string[];
tags: ObjectId[];
data: FileInfo[];
additional_file: FileInfo[];
hidden?: boolean;
Expand Down Expand Up @@ -384,6 +390,7 @@ export interface ScoreboardNode {
style?: string;
hover?: string;
}

export type ScoreboardRow = ScoreboardNode[] & { raw?: any };

export type PenaltyRules = Dictionary<number>;
Expand Down Expand Up @@ -541,6 +548,7 @@ export interface TokenDoc {
createAt: Date;
updateAt: Date;
expireAt: Date;

[key: string]: any;
}

Expand Down Expand Up @@ -647,6 +655,7 @@ export interface Task {
type: string;
subType?: string;
priority: number;

[key: string]: any;
}

Expand All @@ -655,6 +664,7 @@ export interface Schedule {
type: string;
subType?: string;
executeAfter: Date;

[key: string]: any;
}

Expand Down Expand Up @@ -795,6 +805,7 @@ export interface ProblemSearchResponse {
total: number;
countRelation: 'eq' | 'gte';
}

export interface ProblemSearchOptions {
limit?: number;
skip?: number;
Expand All @@ -813,6 +824,7 @@ export interface Lib extends Record<string, any> {
}

export type UIInjectableFields = 'ProblemAdd' | 'Notification' | 'Nav' | 'UserDropdown' | 'DomainManage' | 'ControlPanel';

export interface UI {
template: Record<string, string>;
nodes: Record<UIInjectableFields, any[]>;
Expand Down
4 changes: 4 additions & 0 deletions packages/hydrooj/src/model/document.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable object-curly-newline */
import assert from 'assert';
import { bulletin, plist } from 'codemate-plugin';
import { TagDoc } from 'codemate-plugin/plugins/tags-entity/model';
import { Filter, FindCursor, ObjectId, OnlyFieldsOfType, PushOperator, UpdateFilter } from 'mongodb';
import { Context } from '../context';
import { Content, ContestClarificationDoc, DiscussionDoc, DiscussionReplyDoc, ProblemDoc, ProblemStatusDoc, Tdoc, TrainingDoc } from '../interface';
Expand Down Expand Up @@ -29,6 +30,7 @@ export const TYPE_TRAINING: 40 = 40;
/** @deprecated use `TYPE_CONTEST` with rule `homework` instead. */
export const TYPE_HOMEWORK: 60 = 60;
export const TYPE_BULLETIN: 80 = 80;
export const TYPE_TAGS: 90 = 90;

export interface DocType {
[TYPE_PROBLEM]: ProblemDoc;
Expand All @@ -42,6 +44,7 @@ export interface DocType {
[TYPE_TRAINING]: TrainingDoc;
[TYPE_SYSTEM_PLIST]: plist.ProblemList;
[TYPE_BULLETIN]: bulletin.BulletinDoc;
[TYPE_TAGS]: TagDoc;
}

export interface DocStatusType {
Expand Down Expand Up @@ -550,4 +553,5 @@ global.Hydro.model.document = {
TYPE_PROBLEM_SOLUTION,
TYPE_TRAINING,
TYPE_BULLETIN,
TYPE_TAGS,
};
3 changes: 3 additions & 0 deletions packages/hydrooj/src/model/problem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ import storage from './storage';
import user from './user';

export interface ProblemDoc extends Document {}

export type Field = keyof ProblemDoc;

const logger = new Logger('problem');

function sortable(source: string) {
return source.replace(/(\d+)/g, (str) => (str.length >= 6 ? str : '0'.repeat(6 - str.length) + str));
}
Expand Down Expand Up @@ -86,6 +88,7 @@ export class ProblemModel {
nSubmit: 0,
nAccept: 0,
tag: [],
tags: [],
data: [],
additional_file: [],
stats: {},
Expand Down
20 changes: 20 additions & 0 deletions packages/hydrooj/src/upgrade.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-await-in-loop */
/* eslint-disable @typescript-eslint/naming-convention */
import TagModel from 'codemate-plugin/plugins/tags-entity/model';
import yaml from 'js-yaml';
import { pick } from 'lodash';
import moment from 'moment-timezone';
Expand Down Expand Up @@ -226,6 +227,7 @@ const scripts: UpgradeScript[] = [
async function _51_52() {
const mapping: Record<string, number> = {};
const isStringPid = (i: string) => i.toString().includes(':');

async function getProblem(domainId: string, target: string) {
if (!target.toString().includes(':')) return await problem.get(domainId, target);
const l = `${domainId}/${target}`;
Expand All @@ -235,6 +237,7 @@ const scripts: UpgradeScript[] = [
mapping[l] = docId;
return await problem.get(domainId, docId);
}

const cursor = db.collection('document').find({ docType: document.TYPE_CONTEST });
for await (const doc of cursor) {
const pids = [];
Expand Down Expand Up @@ -297,9 +300,11 @@ const scripts: UpgradeScript[] = [
},
async function _54_55() {
const bulk = db.collection('document').initializeUnorderedBulkOp();

function sortable(source: string) {
return source.replace(/(\d+)/g, (str) => (str.length >= 6 ? str : '0'.repeat(6 - str.length) + str));
}

await iterateAllProblem(['pid', '_id'], async (pdoc) => {
bulk.find({ _id: pdoc._id }).updateOne({ $set: { sort: sortable(pdoc.pid || `P${pdoc.docId}`) } });
});
Expand Down Expand Up @@ -691,6 +696,21 @@ const scripts: UpgradeScript[] = [
}
return true;
},
async function _91_92() {
// iterate all tags, and add a tag document according to it.
const tagMap = new Map<string, ObjectId>();
return await iterateAllProblem(['tag'], async (pdoc) => {
const _tags = [];
for (const tag of pdoc.tag) {
if (!tagMap.has(tag)) {
const docId = await TagModel.add(pdoc.domainId, { 'zh-cn': tag }, [], { 'zh-cn': tag });
tagMap.set(tag, docId);
}
_tags.push(tagMap.get(tag));
}
await problem.edit(pdoc.domainId, pdoc.docId, { tags: _tags });
});
},
];

export default scripts;