mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 09:36:08 +08:00
系统设置中自定义UI的支持
This commit is contained in:
1 parent
11368bc831
commit
1722fbfaca
21 files changed
+284
-140
No files matched your search
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
// 使用 IntelliSense 了解相关属性。
|
||||||
|
// 悬停以查看现有属性的描述。
|
||||||
|
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/.build/app.js",
|
||||||
|
"preLaunchTask": "npm: build",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/dist/**/*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "build",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": [],
|
||||||
|
"label": "npm: build",
|
||||||
|
"detail": "node build --development"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -200,7 +200,7 @@ class ContestEditHandler extends ContestHandler {
|
|||||||
class ContestProblemHandler extends ContestHandler {
|
class ContestProblemHandler extends ContestHandler {
|
||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID)
|
||||||
@param('pid', Types.UnsignedInt)
|
@param('pid', Types.UnsignedInt)
|
||||||
async prepare(domainId: string, tid: ObjectID, pid: number) {
|
async _prepare(domainId: string, tid: ObjectID, pid: number) {
|
||||||
[this.tdoc, this.pdoc] = await Promise.all([
|
[this.tdoc, this.pdoc] = await Promise.all([
|
||||||
contest.get(domainId, tid),
|
contest.get(domainId, tid),
|
||||||
problem.get(domainId, pid, this.user._id),
|
problem.get(domainId, pid, this.user._id),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
|
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
UserNotFoundError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import * as bus from '../service/bus';
|
import * as bus from '../service/bus';
|
||||||
import {
|
import {
|
||||||
@@ -141,7 +142,7 @@ class HomeSecurityHandler extends Handler {
|
|||||||
async postChangeMail({ domainId, currentPassword, mail }) {
|
async postChangeMail({ domainId, currentPassword, mail }) {
|
||||||
this.limitRate('send_mail', 3600, 30);
|
this.limitRate('send_mail', 3600, 30);
|
||||||
this.user.checkPassword(currentPassword);
|
this.user.checkPassword(currentPassword);
|
||||||
const udoc = await user.getByEmail(domainId, mail, true);
|
const udoc = await user.getByEmail(domainId, mail);
|
||||||
if (udoc) throw new UserAlreadyExistError(mail);
|
if (udoc) throw new UserAlreadyExistError(mail);
|
||||||
const [code] = await token.add(
|
const [code] = await token.add(
|
||||||
token.TYPE_CHANGEMAIL,
|
token.TYPE_CHANGEMAIL,
|
||||||
@@ -212,7 +213,7 @@ class UserChangemailWithCodeHandler extends Handler {
|
|||||||
if (!tdoc || tdoc.uid !== this.user._id) {
|
if (!tdoc || tdoc.uid !== this.user._id) {
|
||||||
throw new InvalidTokenError(code);
|
throw new InvalidTokenError(code);
|
||||||
}
|
}
|
||||||
const udoc = await user.getByEmail(domainId, tdoc.mail, true);
|
const udoc = await user.getByEmail(domainId, tdoc.mail);
|
||||||
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
|
if (udoc) throw new UserAlreadyExistError(tdoc.mail);
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
user.setEmail(this.user._id, tdoc.mail),
|
user.setEmail(this.user._id, tdoc.mail),
|
||||||
@@ -253,6 +254,7 @@ class HomeMessagesHandler extends Handler {
|
|||||||
|
|
||||||
async postSend({ uid, content }) {
|
async postSend({ uid, content }) {
|
||||||
const udoc = await user.getById('system', uid);
|
const udoc = await user.getById('system', uid);
|
||||||
|
if (!udoc) throw new UserNotFoundError(uid);
|
||||||
const mdoc = await message.send(this.user._id, uid, content);
|
const mdoc = await message.send(this.user._id, uid, content);
|
||||||
// TODO(twd2): improve here: projection
|
// TODO(twd2): improve here: projection
|
||||||
if (this.user._id !== uid) {
|
if (this.user._id !== uid) {
|
||||||
@@ -262,6 +264,7 @@ class HomeMessagesHandler extends Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async postDeleteMessage({ messageId }) {
|
async postDeleteMessage({ messageId }) {
|
||||||
|
// TODO limitations
|
||||||
await message.del(messageId);
|
await message.del(messageId);
|
||||||
this.back();
|
this.back();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
ValidationError, HomeworkNotLiveError, ProblemNotFoundError,
|
ValidationError, HomeworkNotLiveError, ProblemNotFoundError,
|
||||||
HomeworkNotAttendedError,
|
HomeworkNotAttendedError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
|
import { PenaltyRules } from '../interface';
|
||||||
import {
|
import {
|
||||||
Route, Handler, Types, param,
|
Route, Handler, Types, param,
|
||||||
} from '../service/server';
|
} from '../service/server';
|
||||||
@@ -21,6 +22,12 @@ import * as document from '../model/document';
|
|||||||
import paginate from '../lib/paginate';
|
import paginate from '../lib/paginate';
|
||||||
import { isTitle, isContent } from '../lib/validator';
|
import { isTitle, isContent } from '../lib/validator';
|
||||||
|
|
||||||
|
const validatePenaltyRules = (input: string) => {
|
||||||
|
const s = yaml.safeLoad(input);
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
const convertPenaltyRules = validatePenaltyRules;
|
||||||
|
|
||||||
const HomeworkHandler = contest.ContestHandlerMixin(Handler);
|
const HomeworkHandler = contest.ContestHandlerMixin(Handler);
|
||||||
|
|
||||||
class HomeworkMainHandler extends HomeworkHandler {
|
class HomeworkMainHandler extends HomeworkHandler {
|
||||||
@@ -28,7 +35,7 @@ class HomeworkMainHandler extends HomeworkHandler {
|
|||||||
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK).toArray();
|
const tdocs = await contest.getMulti(domainId, {}, document.TYPE_HOMEWORK).toArray();
|
||||||
const calendar = [];
|
const calendar = [];
|
||||||
for (const tdoc of tdocs) {
|
for (const tdoc of tdocs) {
|
||||||
const cal = { ...tdoc, url: `/homework/${tdoc.docId}` };
|
const cal = { ...tdoc, url: this.url('homework_detail', { tid: tdoc.docId }) };
|
||||||
if (contest.isExtended(tdoc) || contest.isDone(tdoc)) {
|
if (contest.isExtended(tdoc) || contest.isDone(tdoc)) {
|
||||||
cal.endAt = tdoc.endAt;
|
cal.endAt = tdoc.endAt;
|
||||||
cal.penaltySince = tdoc.penaltySince;
|
cal.penaltySince = tdoc.penaltySince;
|
||||||
@@ -211,7 +218,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
|||||||
@param('penaltySinceDate', Types.Date)
|
@param('penaltySinceDate', Types.Date)
|
||||||
@param('penaltySinceTime', Types.Time)
|
@param('penaltySinceTime', Types.Time)
|
||||||
@param('extensionDays', Types.Float)
|
@param('extensionDays', Types.Float)
|
||||||
@param('penaltyRules', Types.String)
|
@param('penaltyRules', Types.String, validatePenaltyRules, convertPenaltyRules)
|
||||||
@param('title', Types.String, isTitle)
|
@param('title', Types.String, isTitle)
|
||||||
@param('content', Types.String, isContent)
|
@param('content', Types.String, isContent)
|
||||||
@param('pids', Types.String)
|
@param('pids', Types.String)
|
||||||
@@ -219,7 +226,7 @@ class HomeworkCreateHandler extends HomeworkHandler {
|
|||||||
async post(
|
async post(
|
||||||
domainId: string, beginAtDate: string, beginAtTime: string,
|
domainId: string, beginAtDate: string, beginAtTime: string,
|
||||||
penaltySinceDate: string, penaltySinceTime: string, extensionDays: number,
|
penaltySinceDate: string, penaltySinceTime: string, extensionDays: number,
|
||||||
penaltyRules: string, title: string, content: string, _pids: string, rated = false,
|
penaltyRules: PenaltyRules, title: string, content: string, _pids: string, rated = false,
|
||||||
) {
|
) {
|
||||||
const pids = _pids.split(',').map((i) => {
|
const pids = _pids.split(',').map((i) => {
|
||||||
if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10);
|
if (isSafeInteger(parseInt(i, 10))) return parseInt(i, 10);
|
||||||
@@ -278,7 +285,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
|||||||
@param('penaltySinceDate', Types.Date)
|
@param('penaltySinceDate', Types.Date)
|
||||||
@param('penaltySinceTime', Types.Time)
|
@param('penaltySinceTime', Types.Time)
|
||||||
@param('extensionDays', Types.Float)
|
@param('extensionDays', Types.Float)
|
||||||
@param('penaltyRules', Types.String)
|
@param('penaltyRules', Types.String, validatePenaltyRules, convertPenaltyRules)
|
||||||
@param('title', Types.String, isTitle)
|
@param('title', Types.String, isTitle)
|
||||||
@param('content', Types.String, isContent)
|
@param('content', Types.String, isContent)
|
||||||
@param('pids', Types.String)
|
@param('pids', Types.String)
|
||||||
@@ -330,6 +337,7 @@ class HomeworkEditHandler extends HomeworkHandler {
|
|||||||
class HomeworkScoreboardHandler extends HomeworkHandler {
|
class HomeworkScoreboardHandler extends HomeworkHandler {
|
||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID)
|
||||||
async get(domainId: string, tid: ObjectID) {
|
async get(domainId: string, tid: ObjectID) {
|
||||||
|
console.log(123);
|
||||||
const [tdoc, rows, udict] = await this.getScoreboard(
|
const [tdoc, rows, udict] = await this.getScoreboard(
|
||||||
domainId, tid, false, document.TYPE_HOMEWORK,
|
domainId, tid, false, document.TYPE_HOMEWORK,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ class JudgeConnectionHandler extends ConnectionHandler {
|
|||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
if (this.processing) {
|
if (this.processing) {
|
||||||
await record.reset(this.processing.domainId, this.processing.rid);
|
await record.reset(this.processing.domainId, this.processing.rid, false);
|
||||||
task.add(this.processing);
|
task.add(this.processing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,6 @@ class SystemSettingHandler extends SystemHandler {
|
|||||||
for (const key in args) {
|
for (const key in args) {
|
||||||
if (typeof args[key] === 'object') {
|
if (typeof args[key] === 'object') {
|
||||||
for (const subkey in args[key]) {
|
for (const subkey in args[key]) {
|
||||||
console.log(`${key}.${subkey}`, set(`${key}.${subkey}`, args[key][subkey]));
|
|
||||||
if (typeof set(`${key}.${subkey}`, args[key][subkey]) !== 'undefined') {
|
if (typeof set(`${key}.${subkey}`, args[key][subkey]) !== 'undefined') {
|
||||||
tasks.push(system.set(`${key}.${subkey}`, set(`${key}.${subkey}`, args[key][subkey])));
|
tasks.push(system.set(`${key}.${subkey}`, set(`${key}.${subkey}`, args[key][subkey])));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ObjectID } from 'mongodb';
|
import { ObjectID } from 'mongodb';
|
||||||
import { PRIV } from '../model/builtin';
|
import { PRIV } from '../model/builtin';
|
||||||
|
import * as system from '../model/system';
|
||||||
import * as file from '../model/file';
|
import * as file from '../model/file';
|
||||||
import * as user from '../model/user';
|
import * as user from '../model/user';
|
||||||
import * as markdown from '../lib/markdown';
|
import * as markdown from '../lib/markdown';
|
||||||
@@ -77,6 +78,18 @@ class SockToken extends Handler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UiSettingsHandler extends Handler {
|
||||||
|
async get() {
|
||||||
|
const [
|
||||||
|
header, nav, headerBackground,
|
||||||
|
] = await system.getMany([
|
||||||
|
'ui.header', 'ui.nav', 'ui.headerBackground',
|
||||||
|
]);
|
||||||
|
this.response.body = await this.renderHTML('extra.css', { header, nav, headerBackground });
|
||||||
|
this.response.type = 'text/css';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function apply() {
|
export async function apply() {
|
||||||
Route('file_download', '/fs/:fileId/:secret', FileDownloadHandler);
|
Route('file_download', '/fs/:fileId/:secret', FileDownloadHandler);
|
||||||
Route('file_download_with_name', '/fs/:fileId/:name/:secret', FileDownloadHandler);
|
Route('file_download_with_name', '/fs/:fileId/:name/:secret', FileDownloadHandler);
|
||||||
@@ -85,6 +98,7 @@ export async function apply() {
|
|||||||
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
|
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
|
||||||
Route('markdown', '/markdown', MarkdownHandler);
|
Route('markdown', '/markdown', MarkdownHandler);
|
||||||
Route('token', '/token', SockToken);
|
Route('token', '/token', SockToken);
|
||||||
|
Route('ui', '/extra.css', UiSettingsHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
apply.updateStatus = function updateStatus(args) {
|
apply.updateStatus = function updateStatus(args) {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class UserRegisterHandler extends Handler {
|
|||||||
|
|
||||||
@param('mail', Types.String, isEmail)
|
@param('mail', Types.String, isEmail)
|
||||||
async post(domainId: string, mail: string) {
|
async post(domainId: string, mail: string) {
|
||||||
if (await user.getByEmail('system', mail, true)) throw new UserAlreadyExistError(mail);
|
if (await user.getByEmail('system', mail)) throw new UserAlreadyExistError(mail);
|
||||||
this.limitRate('send_mail', 3600, 30);
|
this.limitRate('send_mail', 3600, 30);
|
||||||
const t = await token.add(
|
const t = await token.add(
|
||||||
token.TYPE_REGISTRATION,
|
token.TYPE_REGISTRATION,
|
||||||
@@ -158,7 +158,8 @@ class UserDetailHandler extends Handler {
|
|||||||
@param('uid', Types.Int)
|
@param('uid', Types.Int)
|
||||||
async get(domainId: string, uid: number) {
|
async get(domainId: string, uid: number) {
|
||||||
const isSelfProfile = this.user._id === uid;
|
const isSelfProfile = this.user._id === uid;
|
||||||
const udoc = await user.getById(domainId, uid, true);
|
const udoc = await user.getById(domainId, uid);
|
||||||
|
if (!udoc) throw new UserNotFoundError(uid);
|
||||||
const sdoc = await token.getMostRecentSessionByUid(uid);
|
const sdoc = await token.getMostRecentSessionByUid(uid);
|
||||||
const rdocs = await record.getByUid(domainId, uid);
|
const rdocs = await record.getByUid(domainId, uid);
|
||||||
const pdict = await problem.getList(
|
const pdict = await problem.getList(
|
||||||
@@ -310,7 +311,7 @@ class OauthCallbackHandler extends Handler {
|
|||||||
if (args.type === 'github') r = await this.github(args);
|
if (args.type === 'github') r = await this.github(args);
|
||||||
else if (args.type === 'google') r = await this.google(args);
|
else if (args.type === 'google') r = await this.google(args);
|
||||||
else throw new UserFacingError('Oauth type');
|
else throw new UserFacingError('Oauth type');
|
||||||
const udoc = await user.getByEmail('system', r.email, true);
|
const udoc = await user.getByEmail('system', r.email);
|
||||||
if (udoc) {
|
if (udoc) {
|
||||||
this.session.uid = udoc._id;
|
this.session.uid = udoc._id;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+92
-13
@@ -13,21 +13,51 @@ export interface Setting {
|
|||||||
flag: number,
|
flag: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Udoc {
|
||||||
|
udoc: () => any,
|
||||||
|
dudoc: () => any,
|
||||||
|
_id: number,
|
||||||
|
mail: string,
|
||||||
|
uname: string,
|
||||||
|
salt: () => string,
|
||||||
|
hash: () => string,
|
||||||
|
hashType: string,
|
||||||
|
priv: number,
|
||||||
|
regat: Date,
|
||||||
|
loginat: Date,
|
||||||
|
perm: string,
|
||||||
|
role: string,
|
||||||
|
regip: () => string,
|
||||||
|
loginip: () => string,
|
||||||
|
hasPerm: (perm: string) => boolean,
|
||||||
|
hasPriv: (priv: number) => boolean,
|
||||||
|
checkPassword: (password: string) => void,
|
||||||
|
[key: string]: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Udict {
|
||||||
|
[key: number]: Udoc,
|
||||||
|
}
|
||||||
|
|
||||||
export interface Pdoc {
|
export interface Pdoc {
|
||||||
_id: ObjectID
|
_id: ObjectID,
|
||||||
domainId: string
|
domainId: string,
|
||||||
docId: number
|
docId: number,
|
||||||
pid: string
|
pid: string,
|
||||||
owner: number
|
owner: number,
|
||||||
title: string
|
title: string,
|
||||||
content: string
|
content: string,
|
||||||
nSubmit: number
|
nSubmit: number,
|
||||||
nAccept: number
|
nAccept: number,
|
||||||
tag: string[]
|
tag: string[],
|
||||||
category: string[],
|
category: string[],
|
||||||
data: ObjectID | null
|
data?: ObjectID,
|
||||||
hidden: boolean
|
hidden: boolean,
|
||||||
config: string
|
config: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Pdict {
|
||||||
|
[key: string]: Pdoc,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TestCase {
|
export interface TestCase {
|
||||||
@@ -63,6 +93,55 @@ export interface Rdoc {
|
|||||||
ttype?: number,
|
ttype?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ScoreboardNode {
|
||||||
|
type: string,
|
||||||
|
value: string,
|
||||||
|
raw?: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PenaltyRules {
|
||||||
|
[key: string]: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tdoc {
|
||||||
|
_id: ObjectID,
|
||||||
|
domainId: string,
|
||||||
|
docId: ObjectID,
|
||||||
|
docType: number,
|
||||||
|
beginAt: Date,
|
||||||
|
endAt: Date,
|
||||||
|
penaltySince?: Date,
|
||||||
|
penaltyRules?: PenaltyRules,
|
||||||
|
attend: number,
|
||||||
|
title: string,
|
||||||
|
content: string,
|
||||||
|
rule: string,
|
||||||
|
pids: number[],
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContestStat {
|
||||||
|
detail: any,
|
||||||
|
[key: string]: any,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContestRule {
|
||||||
|
TEXT: string,
|
||||||
|
check: (args: any) => any,
|
||||||
|
statusSort: any,
|
||||||
|
showScoreboard: (tdoc: Tdoc, now: Date) => boolean,
|
||||||
|
showRecord: (tdoc: Tdoc, now: Date) => boolean,
|
||||||
|
stat: (tdoc: Tdoc, journal: any[]) => ContestStat,
|
||||||
|
scoreboard: (
|
||||||
|
isExport: boolean, _: (s: string) => string,
|
||||||
|
tdoc: Tdoc, rankedTsdocs: any[] | Generator<any>, udict: Udict, pdict: Pdict
|
||||||
|
) => ScoreboardNode[][],
|
||||||
|
rank: (tsdocs: any[]) => any[] | Generator<any>,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContestRules {
|
||||||
|
[key: string]: ContestRule,
|
||||||
|
}
|
||||||
|
|
||||||
export type ProblemImporter = (url: string, handler: any) =>
|
export type ProblemImporter = (url: string, handler: any) =>
|
||||||
Promise<[Pdoc, fs.ReadStream?]> | [Pdoc, fs.ReadStream?];
|
Promise<[Pdoc, fs.ReadStream?]> | [Pdoc, fs.ReadStream?];
|
||||||
|
|
||||||
|
|||||||
+79
-85
@@ -7,34 +7,18 @@ import {
|
|||||||
} from '../error';
|
} from '../error';
|
||||||
import * as document from './document';
|
import * as document from './document';
|
||||||
import { PERM } from './builtin';
|
import { PERM } from './builtin';
|
||||||
|
import {
|
||||||
|
Tdoc, ContestRule, ContestRules,
|
||||||
|
Udict, ScoreboardNode,
|
||||||
|
} from '../interface';
|
||||||
import * as validator from '../lib/validator';
|
import * as validator from '../lib/validator';
|
||||||
import * as misc from '../lib/misc';
|
import * as misc from '../lib/misc';
|
||||||
import ranked from '../lib/rank';
|
import ranked from '../lib/rank';
|
||||||
|
|
||||||
interface Column {
|
const acm: ContestRule = {
|
||||||
type: string,
|
|
||||||
value: string,
|
|
||||||
raw?: any
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Tdoc {
|
|
||||||
_id: ObjectID,
|
|
||||||
domainId: string,
|
|
||||||
docId: ObjectID,
|
|
||||||
docType: number,
|
|
||||||
beginAt: Date,
|
|
||||||
endAt: Date,
|
|
||||||
penaltySince?: Date,
|
|
||||||
attend: number,
|
|
||||||
title: string,
|
|
||||||
content: string,
|
|
||||||
rule: string,
|
|
||||||
pids: number[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const acm = {
|
|
||||||
TEXT: 'ACM/ICPC',
|
TEXT: 'ACM/ICPC',
|
||||||
check: () => { },
|
check: () => { },
|
||||||
|
statusSort: { accept: -1, time: 1 },
|
||||||
showScoreboard: () => true,
|
showScoreboard: () => true,
|
||||||
showRecord: (tdoc, now) => now > tdoc.endAt,
|
showRecord: (tdoc, now) => now > tdoc.endAt,
|
||||||
stat: (tdoc, journal: any[]) => {
|
stat: (tdoc, journal: any[]) => {
|
||||||
@@ -47,17 +31,18 @@ const acm = {
|
|||||||
if (tdoc.pids.includes(j.pid)
|
if (tdoc.pids.includes(j.pid)
|
||||||
&& !(effective[j.pid] && effective[j.pid].accept)) {
|
&& !(effective[j.pid] && effective[j.pid].accept)) {
|
||||||
effective[j.pid] = j;
|
effective[j.pid] = j;
|
||||||
|
if (!j.accept) naccept[j.pid] = (naccept[j.pid] || 0) + 1;
|
||||||
}
|
}
|
||||||
if (!j.accept) naccept[j.pid]++;
|
|
||||||
}
|
}
|
||||||
function _time(jdoc) {
|
function _time(jdoc) {
|
||||||
const real = jdoc.rid.generationTime - Math.floor(tdoc.beginAt / 1000);
|
const real = jdoc.rid.generationTime - Math.floor(tdoc.beginAt.getTime() / 1000);
|
||||||
const penalty = 20 * 60 * naccept[jdoc.pid];
|
const penalty = 20 * 60 * naccept[jdoc.pid];
|
||||||
return real + penalty;
|
return real + penalty;
|
||||||
}
|
}
|
||||||
for (const j in effective) {
|
for (const key in effective) {
|
||||||
|
const j = effective[key];
|
||||||
detail.push({
|
detail.push({
|
||||||
...effective[j], naccept: naccept[effective[j].pid], time: _time(effective[j]),
|
...j, naccept: naccept[j.pid], time: _time(j),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const d of detail) {
|
for (const d of detail) {
|
||||||
@@ -66,8 +51,8 @@ const acm = {
|
|||||||
}
|
}
|
||||||
return { accept, time, detail };
|
return { accept, time, detail };
|
||||||
},
|
},
|
||||||
scoreboard(isExport: boolean, _: (s: string) => string, tdoc, rankedTsdocs, udict, pdict) {
|
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||||
const columns: Column[] = [
|
const columns: ScoreboardNode[] = [
|
||||||
{ type: 'rank', value: _('Rank') },
|
{ type: 'rank', value: _('Rank') },
|
||||||
{ type: 'user', value: _('User') },
|
{ type: 'user', value: _('User') },
|
||||||
{ type: 'solved_problems', value: _('Solved Problems') },
|
{ type: 'solved_problems', value: _('Solved Problems') },
|
||||||
@@ -78,40 +63,41 @@ const acm = {
|
|||||||
{ type: 'total_time_str', value: _('Total Time') },
|
{ type: 'total_time_str', value: _('Total Time') },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const i in tdoc.pids) {
|
for (let i = 1; i <= tdoc.pids.length; i++) {
|
||||||
|
const pid = tdoc.pids[i - 1];
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
columns.push(
|
columns.push(
|
||||||
{
|
{
|
||||||
type: 'problem_flag',
|
type: 'problem_flag',
|
||||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
value: '#{0} {1}'.format(i, pdict[pid].title),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'problem_time',
|
type: 'problem_time',
|
||||||
value: '#{0} {1}'.format(i + 1, _('Time (Seconds)')),
|
value: '#{0} {1}'.format(i, _('Time (Seconds)')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'problem_time_str',
|
type: 'problem_time_str',
|
||||||
value: '#{0} {1}'.format(i + 1, _('Time')),
|
value: '#{0} {1}'.format(i, _('Time')),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
columns.push({
|
columns.push({
|
||||||
type: 'problem_detail',
|
type: 'problem_detail',
|
||||||
value: '#{0}'.format(i + 1),
|
value: '#{0}'.format(i),
|
||||||
raw: pdict[tdoc.pids[i]],
|
raw: pid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const rows = [columns];
|
const rows = [columns];
|
||||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||||
const tsddict = {};
|
const tsddict = {};
|
||||||
if (tdoc.detail) {
|
if (tsdoc.detail) {
|
||||||
for (const item of tsdoc.journal) tsddict[item.pid] = item;
|
for (const item of tsdoc.detail) tsddict[item.pid] = item;
|
||||||
}
|
}
|
||||||
const row = [];
|
const row = [];
|
||||||
row.push(
|
row.push(
|
||||||
{ type: 'string', value: rank },
|
{ type: 'string', value: rank },
|
||||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] },
|
{ type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid },
|
||||||
{ type: 'string', value: tsdoc.accept || 0 },
|
{ type: 'string', value: tsdoc.accept || 0 },
|
||||||
);
|
);
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
@@ -121,17 +107,17 @@ const acm = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const pid of tdoc.pids) {
|
for (const pid of tdoc.pids) {
|
||||||
let rdoc;
|
let rid;
|
||||||
let colAccepted;
|
let colAccepted;
|
||||||
let colTime;
|
let colTime;
|
||||||
let colTimeStr;
|
let colTimeStr;
|
||||||
if ((tsddict[pid] || {}).accept) {
|
if (tsddict[pid]?.accept) {
|
||||||
rdoc = tsddict[pid].rid;
|
rid = tsddict[pid].rid;
|
||||||
colAccepted = _('Accepted');
|
colAccepted = _('Accepted');
|
||||||
colTime = tsddict[pid].time;
|
colTime = tsddict[pid].time;
|
||||||
colTimeStr = colTime;
|
colTimeStr = misc.formatSeconds(colTime);
|
||||||
} else {
|
} else {
|
||||||
rdoc = null;
|
rid = null;
|
||||||
colAccepted = '-';
|
colAccepted = '-';
|
||||||
colTime = '-';
|
colTime = '-';
|
||||||
colTimeStr = '-';
|
colTimeStr = '-';
|
||||||
@@ -144,20 +130,21 @@ const acm = {
|
|||||||
row.push({
|
row.push({
|
||||||
type: 'record',
|
type: 'record',
|
||||||
value: '{0}\n{1}'.format(colAccepted, colTimeStr),
|
value: '{0}\n{1}'.format(colAccepted, colTimeStr),
|
||||||
raw: rdoc,
|
raw: rid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rows.push(row);
|
|
||||||
}
|
}
|
||||||
|
rows.push(row);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
rank: (tdocs) => ranked(tdocs, (a, b) => a.score === b.score),
|
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
||||||
};
|
};
|
||||||
|
|
||||||
const oi = {
|
const oi: ContestRule = {
|
||||||
TEXT: 'OI',
|
TEXT: 'OI',
|
||||||
check: () => { },
|
check: () => { },
|
||||||
|
statusSort: { score: -1 },
|
||||||
stat: (tdoc, journal) => {
|
stat: (tdoc, journal) => {
|
||||||
const detail = {};
|
const detail = {};
|
||||||
let score = 0;
|
let score = 0;
|
||||||
@@ -176,50 +163,55 @@ const oi = {
|
|||||||
return now > tdoc.endAt;
|
return now > tdoc.endAt;
|
||||||
},
|
},
|
||||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||||
const columns: Column[] = [
|
const columns: ScoreboardNode[] = [
|
||||||
{ type: 'rank', value: _('Rank') },
|
{ type: 'rank', value: _('Rank') },
|
||||||
{ type: 'user', value: _('User') },
|
{ type: 'user', value: _('User') },
|
||||||
{ type: 'total_score', value: _('Total Score') },
|
{ type: 'total_score', value: _('Total Score') },
|
||||||
];
|
];
|
||||||
for (const i in tdoc.pids) {
|
for (let i = 1; i <= tdoc.pids.length; i++) {
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
columns.push({
|
columns.push({
|
||||||
type: 'problem_score',
|
type: 'problem_score',
|
||||||
value: '#{0} {1}'.format(i + 1, pdict[tdoc.pids[i]].title),
|
value: '#{0} {1}'.format(i, pdict[tdoc.pids[i - 1]].title),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
columns.push({
|
columns.push({
|
||||||
type: 'problem_detail',
|
type: 'problem_detail',
|
||||||
value: '#{0}'.format(i + 1),
|
value: '#{0}'.format(i),
|
||||||
raw: pdict[tdoc.pids[i]],
|
raw: tdoc.pids[i - 1],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const rows = [columns];
|
const rows = [columns];
|
||||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||||
const tsddict = {};
|
const tsddict = {};
|
||||||
if (tsdoc.journal) { for (const item of tsdoc.journal) tsddict[item.pid] = item; }
|
if (tsdoc.journal) {
|
||||||
|
for (const item of tsdoc.journal) tsddict[item.pid] = item;
|
||||||
|
}
|
||||||
const row = [];
|
const row = [];
|
||||||
row.push({ type: 'string', value: rank });
|
row.push(
|
||||||
row.push({ type: 'user', value: udict[tsdoc.uid].uname, raw: udict[tsdoc.uid] });
|
{ type: 'string', value: rank },
|
||||||
row.push({ type: 'string', value: tsdoc.score || 0 });
|
{ type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid },
|
||||||
|
{ type: 'string', value: tsdoc.score || 0 },
|
||||||
|
);
|
||||||
for (const pid of tdoc.pids) {
|
for (const pid of tdoc.pids) {
|
||||||
row.push({
|
row.push({
|
||||||
type: 'record',
|
type: 'record',
|
||||||
value: (tsddict[pid] || {}).score || '-',
|
value: tsddict[pid]?.score || '-',
|
||||||
raw: (tsddict[pid] || {}).rid || null,
|
raw: tsddict[pid]?.rid || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
},
|
},
|
||||||
rank: (tdocs) => ranked(tdocs, (a, b) => a.score === b.score),
|
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
||||||
};
|
};
|
||||||
|
|
||||||
const homework = {
|
const homework: ContestRule = {
|
||||||
TEXT: 'Assignment',
|
TEXT: 'Assignment',
|
||||||
check: () => { },
|
check: () => { },
|
||||||
|
statusSort: { penaltyScore: -1, time: 1 },
|
||||||
stat: (tdoc, journal) => {
|
stat: (tdoc, journal) => {
|
||||||
const effective = {};
|
const effective = {};
|
||||||
for (const j of journal) {
|
for (const j of journal) {
|
||||||
@@ -233,6 +225,7 @@ const homework = {
|
|||||||
const real = jdoc.rid.generationTime - tdoc.beginAt.getTime() / 1000;
|
const real = jdoc.rid.generationTime - tdoc.beginAt.getTime() / 1000;
|
||||||
return Math.floor(real);
|
return Math.floor(real);
|
||||||
}
|
}
|
||||||
|
|
||||||
function penaltyScore(jdoc) {
|
function penaltyScore(jdoc) {
|
||||||
const { score } = jdoc;
|
const { score } = jdoc;
|
||||||
const exceedSeconds = Math.floor(
|
const exceedSeconds = Math.floor(
|
||||||
@@ -266,7 +259,7 @@ const homework = {
|
|||||||
showScoreboard: () => true,
|
showScoreboard: () => true,
|
||||||
showRecord: () => true,
|
showRecord: () => true,
|
||||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||||
const columns: Column[] = [
|
const columns: ScoreboardNode[] = [
|
||||||
{ type: 'rank', value: _('Rank') },
|
{ type: 'rank', value: _('Rank') },
|
||||||
{ type: 'user', value: _('User') },
|
{ type: 'user', value: _('User') },
|
||||||
{ type: 'total_score', value: _('Score') },
|
{ type: 'total_score', value: _('Score') },
|
||||||
@@ -278,32 +271,32 @@ const homework = {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
columns.push({ type: 'total_time_str', value: _('Total Time') });
|
||||||
for (const index in tdoc.pids) {
|
for (let i = 1; i <= tdoc.pids.length; i++) {
|
||||||
const pid = tdoc.pids[index];
|
const pid = tdoc.pids[i - 1];
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
columns.push(
|
columns.push(
|
||||||
{
|
{
|
||||||
type: 'problem_score',
|
type: 'problem_score',
|
||||||
value: '#{0} {1}'.format(index + 1, pdict[pid].title),
|
value: '#{0} {1}'.format(i, pdict[pid].title),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'problem_original_score',
|
type: 'problem_original_score',
|
||||||
value: '#{0} {1}'.format(index + 1, _('Original Score')),
|
value: '#{0} {1}'.format(i, _('Original Score')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'problem_time',
|
type: 'problem_time',
|
||||||
value: '#{0} {1}'.format(index + 1, _('Time (Seconds)')),
|
value: '#{0} {1}'.format(i, _('Time (Seconds)')),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'problem_time_str',
|
type: 'problem_time_str',
|
||||||
value: '#{0} {1}'.format(index + 1, _('Time')),
|
value: '#{0} {1}'.format(i, _('Time')),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
columns.push({
|
columns.push({
|
||||||
type: 'problem_detail',
|
type: 'problem_detail',
|
||||||
value: '#{0}'.format(index + 1),
|
value: '#{0}'.format(i),
|
||||||
raw: pdict[pid],
|
raw: pid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -318,7 +311,7 @@ const homework = {
|
|||||||
{
|
{
|
||||||
type: 'user',
|
type: 'user',
|
||||||
value: udict[tsdoc.uid].uname,
|
value: udict[tsdoc.uid].uname,
|
||||||
raw: udict[tsdoc.uid],
|
raw: tsdoc.uid,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -331,10 +324,10 @@ const homework = {
|
|||||||
}
|
}
|
||||||
row.push({ type: 'string', value: misc.formatSeconds(tsdoc.time || 0) });
|
row.push({ type: 'string', value: misc.formatSeconds(tsdoc.time || 0) });
|
||||||
for (const pid of tdoc.pids) {
|
for (const pid of tdoc.pids) {
|
||||||
const rdoc = (tsddict[pid] || {}).rid;
|
const rid = tsddict[pid]?.rid;
|
||||||
const colScore = (tsddict[pid] || {}).penaltyScore || '-';
|
const colScore = tsddict[pid]?.penaltyScore || '-';
|
||||||
const colOriginalScore = (tsddict[pid] || {}).score || '-';
|
const colOriginalScore = tsddict[pid]?.score || '-';
|
||||||
const colTime = (tsddict[pid] || {}).time || '-';
|
const colTime = tsddict[pid]?.time || '-';
|
||||||
const colTimeStr = colTime !== '-' ? misc.formatSeconds(colTime || 0) : '-';
|
const colTimeStr = colTime !== '-' ? misc.formatSeconds(colTime || 0) : '-';
|
||||||
if (isExport) {
|
if (isExport) {
|
||||||
row.push(
|
row.push(
|
||||||
@@ -347,7 +340,7 @@ const homework = {
|
|||||||
row.push({
|
row.push({
|
||||||
type: 'record',
|
type: 'record',
|
||||||
value: '{0} / {1}\n{2}'.format(colScore, colOriginalScore, colTimeStr),
|
value: '{0} / {1}\n{2}'.format(colScore, colOriginalScore, colTimeStr),
|
||||||
raw: rdoc,
|
raw: rid,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,10 +351,14 @@ const homework = {
|
|||||||
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
rank: (tsdocs) => ranked(tsdocs, (a, b) => a.score === b.score),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RULES = {
|
export const RULES: ContestRules = {
|
||||||
acm, oi, homework,
|
acm, oi, homework,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function _getStatusJournal(tsdoc) {
|
||||||
|
return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime));
|
||||||
|
}
|
||||||
|
|
||||||
export function add(
|
export function add(
|
||||||
domainId: string, title: string, content: string, owner: number,
|
domainId: string, title: string, content: string, owner: number,
|
||||||
rule: string, beginAt = new Date(), endAt = new Date(), pids = [],
|
rule: string, beginAt = new Date(), endAt = new Date(), pids = [],
|
||||||
@@ -407,11 +404,14 @@ export async function updateStatus(
|
|||||||
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number,
|
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number,
|
||||||
accept = false, score = 0, type = document.TYPE_CONTEST,
|
accept = false, score = 0, type = document.TYPE_CONTEST,
|
||||||
) {
|
) {
|
||||||
await get(domainId, tid, type);
|
const tdoc = await get(domainId, tid, type);
|
||||||
const tsdoc = await document.revPushStatus(domainId, type, tid, uid, 'journal', {
|
const tsdoc = await document.revPushStatus(domainId, type, tid, uid, 'journal', {
|
||||||
rid, pid, accept, score,
|
rid, pid, accept, score,
|
||||||
});
|
});
|
||||||
if (!tsdoc.attend) throw new ContestNotAttendedError(tid, uid);
|
if (!tsdoc.attend) throw new ContestNotAttendedError(tid, uid);
|
||||||
|
const journal = _getStatusJournal(tsdoc);
|
||||||
|
const stats = RULES[tdoc.rule].stat(tdoc, journal);
|
||||||
|
return await document.revSetStatus(domainId, type, tid, uid, tsdoc.rev, { journal, ...stats });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStatus(
|
export function getStatus(
|
||||||
@@ -498,13 +498,11 @@ export const ContestHandlerMixin = (c) => class extends c {
|
|||||||
async getScoreboard(
|
async getScoreboard(
|
||||||
domainId: string, tid: ObjectID,
|
domainId: string, tid: ObjectID,
|
||||||
isExport = false, docType = document.TYPE_CONTEST,
|
isExport = false, docType = document.TYPE_CONTEST,
|
||||||
) {
|
): Promise<[Tdoc, ScoreboardNode[][], Udict]> {
|
||||||
const tdoc = await get(domainId, tid, docType);
|
const tdoc = await get(domainId, tid, docType);
|
||||||
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||||
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType)
|
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).toArray();
|
||||||
.sort(RULES[tdoc.rule].statusSort).toArray();
|
const uids = tsdocs.map((tsdoc) => tsdoc.uid);
|
||||||
const uids = [];
|
|
||||||
for (const tsdoc of tsdocs) uids.push(tsdoc.uid);
|
|
||||||
const [udict, pdict] = await Promise.all([
|
const [udict, pdict] = await Promise.all([
|
||||||
user.getList(domainId, uids),
|
user.getList(domainId, uids),
|
||||||
problem.getList(domainId, tdoc.pids, true),
|
problem.getList(domainId, tdoc.pids, true),
|
||||||
@@ -535,10 +533,6 @@ export function getMulti(domainId: string, query = {}, type = document.TYPE_CONT
|
|||||||
return document.getMulti(domainId, type, query);
|
return document.getMulti(domainId, type, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getStatusJournal(tsdoc) {
|
|
||||||
return tsdoc.journal.sort((a, b) => (a.rid.generationTime - b.rid.generationTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAndListStatus(
|
export async function getAndListStatus(
|
||||||
domainId: string, tid: ObjectID, docType = document.TYPE_CONTEST,
|
domainId: string, tid: ObjectID, docType = document.TYPE_CONTEST,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -307,11 +307,10 @@ export async function revSetStatus(
|
|||||||
};
|
};
|
||||||
const update = { $set: args, $inc: { rev: 1 } };
|
const update = { $set: args, $inc: { rev: 1 } };
|
||||||
if (returnDoc) {
|
if (returnDoc) {
|
||||||
const res = await coll.findOneAndUpdate(filter, update, { returnOriginal: false });
|
const res = await collStatus.findOneAndUpdate(filter, update, { returnOriginal: false });
|
||||||
return res.value;
|
return res.value;
|
||||||
}
|
}
|
||||||
const res = await coll.updateOne(filter, update);
|
return await collStatus.updateOne(filter, update);
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureIndexes() {
|
export async function ensureIndexes() {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { STATUS } from './builtin';
|
|||||||
import * as file from './file';
|
import * as file from './file';
|
||||||
import * as document from './document';
|
import * as document from './document';
|
||||||
import * as domain from './domain';
|
import * as domain from './domain';
|
||||||
import { Pdoc } from '../interface';
|
import { Pdoc, Pdict } from '../interface';
|
||||||
import { ProblemNotFoundError } from '../error';
|
import { ProblemNotFoundError } from '../error';
|
||||||
import readConfig from '../lib/readConfig';
|
import readConfig from '../lib/readConfig';
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ export async function random<T>(domainId: string, query: FilterQuery<T>) {
|
|||||||
export async function getList(
|
export async function getList(
|
||||||
domainId: string, pids: Array<number | string>,
|
domainId: string, pids: Array<number | string>,
|
||||||
getHidden = false, doThrow = true,
|
getHidden = false, doThrow = true,
|
||||||
) {
|
): Promise<Pdict> {
|
||||||
pids = Array.from(new Set(pids));
|
pids = Array.from(new Set(pids));
|
||||||
const r = {};
|
const r = {};
|
||||||
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
|
const q: any = { $or: [{ docId: { $in: pids } }, { pid: { $in: pids } }] };
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ export async function update(
|
|||||||
return res.value;
|
return res.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reset(domainId: string, rid: ObjectID) {
|
export function reset(domainId: string, rid: ObjectID, isRejudge: boolean) {
|
||||||
return update(domainId, rid, {
|
const upd: any = {
|
||||||
score: 0,
|
score: 0,
|
||||||
status: STATUS.STATUS_WAITING,
|
status: STATUS.STATUS_WAITING,
|
||||||
time: 0,
|
time: 0,
|
||||||
@@ -123,8 +123,9 @@ export function reset(domainId: string, rid: ObjectID) {
|
|||||||
compilerTexts: [],
|
compilerTexts: [],
|
||||||
judgeAt: null,
|
judgeAt: null,
|
||||||
judger: null,
|
judger: null,
|
||||||
rejudged: true,
|
};
|
||||||
});
|
if (isRejudge) upd.rejudged = true;
|
||||||
|
return update(domainId, rid, upd);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function count(domainId: string, query: any) {
|
export function count(domainId: string, query: any) {
|
||||||
@@ -176,7 +177,7 @@ export async function judge(domainId: string, rid: ObjectID) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function rejudge(domainId: string, rid: ObjectID) {
|
export async function rejudge(domainId: string, rid: ObjectID) {
|
||||||
await reset(domainId, rid);
|
await reset(domainId, rid, true);
|
||||||
const rdoc = await get(domainId, rid);
|
const rdoc = await get(domainId, rid);
|
||||||
const pdoc = await problem.get(domainId, rdoc.pid);
|
const pdoc = await problem.get(domainId, rdoc.pid);
|
||||||
await task.add({
|
await task.add({
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ SystemSetting(
|
|||||||
Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'),
|
Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'),
|
||||||
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
|
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
|
||||||
Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'),
|
Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'),
|
||||||
|
Setting('setting_ui', 'ui.header', null, null, 'text', 'Header Logo'),
|
||||||
|
Setting('setting_ui', 'ui.headerBackground', null, null, 'text', 'Header Background'),
|
||||||
|
Setting('setting_ui', 'ui.nav', null, null, 'text', 'Nav Logo'),
|
||||||
Setting('setting_oauth', 'oauth.githubappid', null, null, 'text', 'Github Oauth AppID'),
|
Setting('setting_oauth', 'oauth.githubappid', null, null, 'text', 'Github Oauth AppID'),
|
||||||
Setting('setting_oauth', 'oauth.githubsecret', null, null, 'text', 'Github Oauth Secret', null, FLAG_SECRET),
|
Setting('setting_oauth', 'oauth.githubsecret', null, null, 'text', 'Github Oauth Secret', null, FLAG_SECRET),
|
||||||
Setting('setting_oauth', 'oauth.googleappid', null, null, 'text', 'Google Oauth ClientID', null),
|
Setting('setting_oauth', 'oauth.googleappid', null, null, 'text', 'Google Oauth ClientID', null),
|
||||||
|
|||||||
+12
-14
@@ -6,6 +6,7 @@ import { BUILTIN_ROLES, BUILTIN_USERS, PRIV } from './builtin';
|
|||||||
import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error';
|
import { UserNotFoundError, UserAlreadyExistError, LoginError } from '../error';
|
||||||
import pwhash from '../lib/hash.hydro';
|
import pwhash from '../lib/hash.hydro';
|
||||||
import * as db from '../service/db';
|
import * as db from '../service/db';
|
||||||
|
import { Udoc, Udict } from '../interface';
|
||||||
|
|
||||||
const coll = db.collection('user');
|
const coll = db.collection('user');
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ export function setPassword(uid: number, password: string) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export class User {
|
export class User implements Udoc {
|
||||||
udoc: () => any;
|
udoc: () => any;
|
||||||
|
|
||||||
dudoc: () => any;
|
dudoc: () => any;
|
||||||
@@ -85,7 +86,7 @@ export class User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasPriv(p: number) {
|
hasPriv(p: number) {
|
||||||
return this.priv & p;
|
return !!(this.priv & p);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPassword(password: string) {
|
checkPassword(password: string) {
|
||||||
@@ -109,19 +110,16 @@ export async function getInDomain(domainId: string, udoc: User) {
|
|||||||
return dudoc;
|
return dudoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getById(domainId: string, _id: number, throwError = false) {
|
export async function getById(domainId: string, _id: number): Promise<Udoc | null> {
|
||||||
const udoc = _id === 0 || _id === 1
|
const udoc = _id === 0 || _id === 1
|
||||||
? BUILTIN_USERS[_id]
|
? BUILTIN_USERS[_id]
|
||||||
: await coll.findOne({ _id });
|
: await coll.findOne({ _id });
|
||||||
if (!udoc) {
|
if (!udoc) return null;
|
||||||
if (throwError) throw new UserNotFoundError(_id);
|
|
||||||
else return null;
|
|
||||||
}
|
|
||||||
const dudoc = await getInDomain(domainId, udoc);
|
const dudoc = await getInDomain(domainId, udoc);
|
||||||
return new User(udoc, dudoc);
|
return new User(udoc, dudoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getList(domainId: string, uids: number[]) {
|
export async function getList(domainId: string, uids: number[]): Promise<Udict> {
|
||||||
const _uids = new Set(uids);
|
const _uids = new Set(uids);
|
||||||
const r = {};
|
const r = {};
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
@@ -129,7 +127,9 @@ export async function getList(domainId: string, uids: number[]) {
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getByUname(domainId: string, uname: string, ignoreMissing = false) {
|
export async function getByUname(
|
||||||
|
domainId: string, uname: string, ignoreMissing = false,
|
||||||
|
): Promise<Udoc> {
|
||||||
const unameLower = uname.trim().toLowerCase();
|
const unameLower = uname.trim().toLowerCase();
|
||||||
const udoc = (unameLower === 'guest')
|
const udoc = (unameLower === 'guest')
|
||||||
? BUILTIN_USERS[0]
|
? BUILTIN_USERS[0]
|
||||||
@@ -144,17 +144,14 @@ export async function getByUname(domainId: string, uname: string, ignoreMissing
|
|||||||
return new User(udoc, dudoc);
|
return new User(udoc, dudoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getByEmail(domainId: string, mail: string, ignoreMissing = false) {
|
export async function getByEmail(domainId: string, mail: string): Promise<Udoc | null> {
|
||||||
const mailLower = mail.trim().toLowerCase();
|
const mailLower = mail.trim().toLowerCase();
|
||||||
const udoc = (mailLower === 'guest@hydro.local')
|
const udoc = (mailLower === 'guest@hydro.local')
|
||||||
? BUILTIN_USERS[0]
|
? BUILTIN_USERS[0]
|
||||||
: mailLower === 'hydro@hydro.local'
|
: mailLower === 'hydro@hydro.local'
|
||||||
? BUILTIN_USERS[1]
|
? BUILTIN_USERS[1]
|
||||||
: await coll.findOne({ mailLower });
|
: await coll.findOne({ mailLower });
|
||||||
if (!udoc) {
|
if (!udoc) return null;
|
||||||
if (ignoreMissing) return null;
|
|
||||||
throw new UserNotFoundError(mail);
|
|
||||||
}
|
|
||||||
const dudoc = await getInDomain(domainId, udoc);
|
const dudoc = await getInDomain(domainId, udoc);
|
||||||
return new User(udoc, dudoc);
|
return new User(udoc, dudoc);
|
||||||
}
|
}
|
||||||
@@ -172,6 +169,7 @@ export function setEmail(uid: number, mail: string) {
|
|||||||
|
|
||||||
export async function changePassword(uid: number, currentPassword: string, newPassword: string) {
|
export async function changePassword(uid: number, currentPassword: string, newPassword: string) {
|
||||||
const udoc = await getById('system', uid);
|
const udoc = await getById('system', uid);
|
||||||
|
if (!udoc) throw new UserNotFoundError(uid);
|
||||||
udoc.checkPassword(currentPassword);
|
udoc.checkPassword(currentPassword);
|
||||||
const salt = String.random();
|
const salt = String.random();
|
||||||
return await coll.findOneAndUpdate(
|
return await coll.findOneAndUpdate(
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export async function run({ username = '', password = '' } = {}) {
|
|||||||
await Promise.all(tasks);
|
await Promise.all(tasks);
|
||||||
await domain.add('system', 0, 'Hydro', true);
|
await domain.add('system', 0, 'Hydro', true);
|
||||||
if (username && password) {
|
if (username && password) {
|
||||||
const udoc = await user.getById('system', -1, false);
|
const udoc = await user.getById('system', -1);
|
||||||
if (!udoc) {
|
if (!udoc) {
|
||||||
await user.create({
|
await user.create({
|
||||||
uid: -1,
|
uid: -1,
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ export const Handler = HandlerMixin(class {
|
|||||||
this.getBdoc(),
|
this.getBdoc(),
|
||||||
]);
|
]);
|
||||||
[this.user] = await Promise.all([
|
[this.user] = await Promise.all([
|
||||||
user.getById(domainId, this.session.uid, true),
|
user.getById(domainId, this.session.uid),
|
||||||
this.getCsrfToken(),
|
this.getCsrfToken(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,15 @@
|
|||||||
<table class="data-table">
|
<table class="data-table">
|
||||||
<colgroup>
|
<colgroup>
|
||||||
{%- for column in rows[0] -%}
|
{%- for column in rows[0] -%}
|
||||||
<col class="col--{{ column['type'] }}">
|
<col class="col--{{ column.type }}">
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</colgroup>
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{%- for column in rows[0] -%}
|
{%- for column in rows[0] -%}
|
||||||
<th class="col--{{ column['type'] }}">
|
<th class="col--{{ column.type }}">
|
||||||
{% if column.type == 'problem_detail' %}
|
{% if column.type == 'problem_detail' %}
|
||||||
<a href="{{ url('contest_detail_problem', tid=tdoc.docId, pid=column.raw._id) }}" data-tooltip="{{ column['raw']['title'] }}">{{ column['value'] }}</a>
|
<a href="{{ url('contest_detail_problem', tid=tdoc.docId, pid=column.raw) }}" data-tooltip="{{ pdict[column.raw].title }}">{{ column.value }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ column.value }}
|
{{ column.value }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
{%- for column in row -%}
|
{%- for column in row -%}
|
||||||
<td class="col--{{ rows[0][loop.index0]['type'] }}">
|
<td class="col--{{ rows[0][loop.index0]['type'] }}">
|
||||||
{% if column.type == 'user' %}
|
{% if column.type == 'user' %}
|
||||||
{{ user.render_inline(column.raw, badge=false) }}
|
{{ user.render_inline(udict[column.raw], badge=false) }}
|
||||||
{% elif column.type == 'record' and column.raw %}
|
{% elif column.type == 'record' and column.raw %}
|
||||||
<a href="{{ url('record_detail', rid=column.raw) }}">{{ column.value }}</a>
|
<a href="{{ url('record_detail', rid=column.raw) }}">{{ column.value }}</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* {% if header %} */
|
||||||
|
.header__logo {
|
||||||
|
background: url({{ header }}) !important
|
||||||
|
}
|
||||||
|
/* {% endif %} */
|
||||||
|
/* {% if headerBackground %} */
|
||||||
|
.header {
|
||||||
|
background: url({{ headerBackground }}) !important
|
||||||
|
}
|
||||||
|
/* {% endif %} */
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<meta name="theme-color" content="#56758f">
|
<meta name="theme-color" content="#56758f">
|
||||||
|
|
||||||
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
<link rel="stylesheet" media="all" href="{{ static_url('vj4.css') }}">
|
||||||
|
<link rel="stylesheet" media="all" href="{{ static_url('extra.css') }}">
|
||||||
<title>{{ handler.renderTitle(page_name) }}</title>
|
<title>{{ handler.renderTitle(page_name) }}</title>
|
||||||
{% if not isIE(handler.request.headers['user-agent']) %}
|
{% if not isIE(handler.request.headers['user-agent']) %}
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in new issue
Block a user