better types & use attend instead of enroll

This commit is contained in:
undefined committed 2020-09-07 12:43:09 +08:00
1 parent e7428c8f79
commit 3dd4c1c994
17 files changed
+141 -82

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.13.4",
"version": "2.13.5",
"bin": "bin/hydrooj.js",
"main": "dist/loader.js",
"typings": "dist/loader.d.ts",
+1 -1
View File
@@ -159,7 +159,7 @@ class HomeSecurityHandler extends Handler {
if (udoc) throw new UserAlreadyExistError(email);
const [code] = await token.add(
token.TYPE_CHANGEMAIL,
await system.get('changemail_token_expire_seconds'),
await system.get('changemail_token_expire_seconds') as number,
{ uid: this.user._id, email },
);
const m = await this.renderHTML('user_changemail_mail.html', {
+3 -14
View File
@@ -13,10 +13,10 @@ import {
} from '../service/server';
import { Tdoc } from '../interface';
async function _parseDagJson(domainId, dag) {
async function _parseDagJson(domainId: string, _dag: string): Promise<Tdoc['dag']> {
const parsed = [];
try {
dag = JSON.parse(dag);
const dag = JSON.parse(_dag);
assert(dag instanceof Array, 'dag must be an array');
const ids = new Set(dag.map((s) => s._id));
assert(dag.length === ids.size, '_id must be unique');
@@ -177,16 +177,6 @@ class TrainingCreateHandler extends Handler {
const pids = training.getPids(dag);
assert(pids.length, new ValidationError('dag'));
const pdocs = await problem.getMulti(domainId, { docId: { $in: pids } }).sort('_id', 1).toArray();
const existPids = pdocs.map((pdoc) => pdoc.docId);
const existPnames = pdocs.map((pdoc) => pdoc.pid);
if (pids.length !== existPids.length) {
for (const pid of pids) {
assert(
existPids.includes(pid) || existPnames.includes(pid),
new ProblemNotFoundError(pid),
);
}
}
for (const pdoc of pdocs) {
if (pdoc.hidden) this.checkPerm(PERM.PERM_VIEW_PROBLEM_HIDDEN);
}
@@ -242,11 +232,10 @@ class TrainingEditHandler extends Handler {
],
}).sort('_id', 1).toArray();
const existPids = pdocs.map((pdoc) => pdoc.docId);
const existPnames = pdocs.map((pdoc) => pdoc.pid);
if (pids.length !== existPids.length) {
for (const pid in pids) {
assert(
existPids.includes(pid) || existPnames.includes(pid),
existPids.includes(parseInt(pid, 10)),
new ProblemNotFoundError(pid),
);
}
+2 -2
View File
@@ -62,7 +62,7 @@ class UserRegisterHandler extends Handler {
this.limitRate('send_mail', 3600, 30);
const t = await token.add(
token.TYPE_REGISTRATION,
await system.get('registration_token_expire_seconds'),
await system.get('registration_token_expire_seconds') as number,
{ mail },
);
if (await system.get('smtp.user')) {
@@ -118,7 +118,7 @@ class UserLostPassHandler extends Handler {
if (!udoc) throw new UserNotFoundError(mail);
const [tid] = await token.add(
token.TYPE_LOSTPASS,
await system.get('lostpass_token_expire_seconds'),
await system.get('lostpass_token_expire_seconds') as number,
{ uid: udoc._id },
);
const m = await this.renderHTML('user_lostpass_mail', { url: `lostpass/${tid}`, uname: udoc.uname });
+35 -1
View File
@@ -3,6 +3,15 @@ import { ObjectID, GridFSBucket } from 'mongodb';
import fs from 'fs';
import { Dictionary, NumericDictionary } from 'lodash';
export type NumberKeys<O> = {
[K in keyof O]: number extends O[K] ? K : never
}[keyof O]
export interface System {
_id: string,
value: string | number,
}
export interface Setting {
family: string,
key: string,
@@ -175,7 +184,7 @@ export interface PretestConfig {
}
export interface ContestInfo {
type: number,
type: 30 | 60,
tid: ObjectID,
}
@@ -293,6 +302,8 @@ export interface Ddoc {
ip: string,
pin: boolean,
highlight: boolean,
updateAt: Date,
nReply: number,
}
// Discussion reply
@@ -379,6 +390,29 @@ export interface JudgeResultBody {
export type PathComponent = [string, string, Dictionary<any>?, boolean?];
export interface Collections {
'blacklist': Bdoc,
'contest': Tdoc,
'domain': DomainDoc,
'domain.user': any,
'record': Rdoc,
'document': any,
'problem': Pdoc,
'user': Udoc,
'check': any,
'message': Mdoc,
'token': TokenDoc,
'file': Ufdoc,
'status': any,
'oauth': any,
'system': System,
'task': any,
'opcount': any,
'fs.chunks': any,
'fs.files': any,
'document.status': any,
}
export interface Model {
blacklist: typeof import('./model/blacklist'),
builtin: typeof import('./model/builtin'),
+1 -1
View File
@@ -12,7 +12,7 @@ export async function sendMail(to: string, subject: string, text: string, html:
system.get('smtp.user'),
system.get('smtp.pass'),
system.get('smtp.from'),
]);
]) as unknown as [string, number, boolean, string, string, string];
const transporter = nodemailer.createTransport({
host, port, secure, auth: { user, pass },
});
+16 -12
View File
@@ -1,4 +1,4 @@
import { ObjectID } from 'mongodb';
import { ObjectID, FilterQuery } from 'mongodb';
import * as user from './user';
import * as problem from './problem';
import * as document from './document';
@@ -13,6 +13,8 @@ import {
import * as misc from '../lib/misc';
import ranked from '../lib/rank';
type Type = 30 | 60;
const acm: ContestRule = {
TEXT: 'ACM/ICPC',
check: () => { },
@@ -356,7 +358,7 @@ function _getStatusJournal(tsdoc) {
export function add(
domainId: string, title: string, content: string, owner: number,
rule: string, beginAt = new Date(), endAt = new Date(), pids = [],
rated = false, data = {}, type = document.TYPE_CONTEST,
rated = false, data: Partial<Tdoc> = {}, type: Type = document.TYPE_CONTEST,
) {
if (!this.RULES[rule]) throw new ValidationError('rule');
if (beginAt >= endAt) throw new ValidationError('beginAt', 'endAt');
@@ -371,7 +373,7 @@ export function add(
export async function edit(
domainId: string, tid: ObjectID,
$set: any, type = document.TYPE_CONTEST,
$set: any, type: Type = document.TYPE_CONTEST,
) {
if ($set.rule) {
if (!this.RULES[$set.rule]) throw new ValidationError('rule');
@@ -383,7 +385,7 @@ export async function edit(
}
export async function get(
domainId: string, tid: ObjectID, type = document.TYPE_CONTEST,
domainId: string, tid: ObjectID, type: Type | -1 = document.TYPE_CONTEST,
) {
let tdoc: Tdoc;
if (type === -1) {
@@ -396,7 +398,7 @@ export async function get(
export async function updateStatus(
domainId: string, tid: ObjectID, uid: number, rid: ObjectID, pid: number,
accept = false, score = 0, type = document.TYPE_CONTEST,
accept = false, score = 0, type: 30 | 60 = document.TYPE_CONTEST,
) {
const tdoc = await get(domainId, tid, type);
const tsdoc = await document.revPushStatus(domainId, type, tid, uid, 'journal', {
@@ -427,7 +429,7 @@ export async function getListStatus(
export async function attend(
domainId: string, tid: ObjectID, uid: number,
type = document.TYPE_CONTEST,
type: Type = document.TYPE_CONTEST,
) {
try {
await document.cappedIncStatus(domainId, type, tid, uid, 'attend', 1, 0, 1);
@@ -491,7 +493,7 @@ export const ContestHandlerMixin = (c) => class extends c {
async getScoreboard(
domainId: string, tid: ObjectID,
isExport = false, docType = document.TYPE_CONTEST,
isExport = false, docType: 30 | 60 = document.TYPE_CONTEST,
): Promise<[Tdoc, ScoreboardNode[][], Udict]> {
const tdoc = await get(domainId, tid, docType);
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
@@ -522,16 +524,18 @@ export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: an
return document.setStatus(domainId, document.TYPE_CONTEST, tid, uid, $set);
}
export function count(domainId: string, query: any, type = document.TYPE_CONTEST) {
export function count(domainId: string, query: any, type: Type = document.TYPE_CONTEST) {
return document.count(domainId, type, query);
}
export function getMulti(domainId: string, query = {}, type = document.TYPE_CONTEST) {
return document.getMulti(domainId, type, query);
export function getMulti<K extends Type & keyof document.DocumentType>(
domainId: string, query: FilterQuery<document.DocumentType[K]> = {}, type?: K,
) {
return document.getMulti(domainId, type || document.TYPE_CONTEST, query);
}
export async function getAndListStatus(
domainId: string, tid: ObjectID, docType = document.TYPE_CONTEST,
domainId: string, tid: ObjectID, docType: Type | -1 = document.TYPE_CONTEST,
): Promise<[Tdoc, any[]]> {
// TODO(iceboy): projection, pagination.
const tdoc = await get(domainId, tid, docType);
@@ -540,7 +544,7 @@ export async function getAndListStatus(
return [tdoc, tsdocs];
}
export async function recalcStatus(domainId: string, tid: ObjectID, type = document.TYPE_CONTEST) {
export async function recalcStatus(domainId: string, tid: ObjectID, type: Type = document.TYPE_CONTEST) {
const [tdoc, tsdocs] = await Promise.all([
document.get(domainId, type, tid),
document.getMultiStatus(domainId, type, { docId: tid }).toArray(),
+57 -27
View File
@@ -1,7 +1,10 @@
import assert from 'assert';
import { ObjectID } from 'mongodb';
import { ObjectID, Cursor, FilterQuery } from 'mongodb';
import * as db from '../service/db';
import * as bus from '../service/bus';
import {
Pdoc, Ddoc, Ufdoc, Drdoc, Tdoc, TrainingDoc, NumberKeys,
} from '../interface';
type DocID = ObjectID | string | number;
@@ -19,15 +22,28 @@ export const TYPE_TRAINING = 40;
export const TYPE_FILE = 50;
export const TYPE_HOMEWORK = 60;
export async function add<T extends DocID>(
export interface DocumentType {
[TYPE_PROBLEM]: Pdoc,
[TYPE_PROBLEM_SOLUTION]: any,
[TYPE_PROBLEM_LIST]: any,
[TYPE_DISCUSSION_NODE]: any,
[TYPE_DISCUSSION]: Ddoc,
[TYPE_DISCUSSION_REPLY]: Drdoc,
[TYPE_CONTEST]: Tdoc,
[TYPE_TRAINING]: TrainingDoc,
[TYPE_FILE]: Ufdoc,
[TYPE_HOMEWORK]: Tdoc,
}
export async function add<T extends DocID, K extends keyof DocumentType>(
domainId: string, content: string, owner: number,
docType: number, docId: T,
docType: K, docId: DocumentType[K]['docId'],
parentType?: number | null, parentId?: DocID,
args?: any,
): Promise<T>
export async function add(
export async function add<K extends keyof DocumentType>(
domainId: string, content: string, owner: number,
docType: number, docId: null,
docType: K, docId: null,
parentType?: number, parentId?: DocID,
args?: any,
): Promise<ObjectID>
@@ -57,11 +73,13 @@ export async function add(
return docId || res.insertedId;
}
export function get(domainId: string, docType: number, docId: DocID) {
export function get<K extends keyof DocumentType>(domainId: string, docType: K, docId: DocumentType[K]['docId']) {
return coll.findOne({ domainId, docType, docId });
}
export async function set(domainId: string, docType: number, docId: DocID, $set: any) {
export async function set<K extends keyof DocumentType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'], $set: Partial<DocumentType[K]>,
): Promise<DocumentType[K]> {
await bus.parallel('document/set', domainId, docType, docId, $set);
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
@@ -71,28 +89,36 @@ export async function set(domainId: string, docType: number, docId: DocID, $set:
return res.value;
}
export function deleteOne(domainId: string, docType: number, docId: DocID) {
export function deleteOne<K extends keyof DocumentType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'],
) {
return Promise.all([
collStatus.deleteMany({ domainId, docType, docId }),
coll.deleteOne({ domainId, docType, docId }),
]);
}
export function deleteMulti(domainId: string, docType: number, args: any = {}) {
return coll.deleteMany({ ...args, domainId, docType });
export function deleteMulti<K extends keyof DocumentType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>,
) {
return coll.deleteMany({ ...query, domainId, docType });
}
export function deleteMultiStatus(domainId: string, docType: number, args: any = {}) {
return collStatus.deleteMany({ ...args, domainId, docType });
export function deleteMultiStatus<K extends keyof DocumentType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>,
) {
return collStatus.deleteMany({ ...query, domainId, docType });
}
export function getMulti(domainId: string, docType: number, args: any = {}) {
return coll.find({ ...args, docType, domainId });
export function getMulti<K extends keyof DocumentType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>,
): Cursor<DocumentType[K]> {
return coll.find({ ...query, docType, domainId });
}
export async function inc(
domainId: string, docType: number, docId: DocID,
key: string, value: number,
export async function inc<K extends keyof DocumentType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'],
key: NumberKeys<DocumentType[K]>, value: number,
) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
@@ -102,9 +128,11 @@ export async function inc(
return res.value;
}
export async function incAndSet(
domainId: string, docType: number, docId: DocID,
key: string, value: number, args: any,
type Q = NumberKeys<DocumentType[10]>
export async function incAndSet<K extends keyof DocumentType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'],
key: NumberKeys<DocumentType[K]>, value: number, args: Partial<DocumentType[K]>,
) {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
@@ -114,13 +142,15 @@ export async function incAndSet(
return res.value;
}
export function count(domainId: string, docType: number, query: any) {
export function count<K extends keyof DocumentType>(
domainId: string, docType: K, query?: FilterQuery<DocumentType[K]>,
) {
return coll.find({ ...query, docType, domainId }).count();
}
export async function push(
domainId: string, docType: number, docId: DocID,
key: string, content: string, owner: number, args: any = {},
export async function push<K extends keyof DocumentType, T extends keyof DocumentType[K]>(
domainId: string, docType: K, docId: DocumentType[K]['docId'],
key: keyof DocumentType[K], content: string, owner: number, args: DocumentType[K][T][0] = {},
) {
const _id = new ObjectID();
const doc = await coll.findOneAndUpdate(
@@ -137,10 +167,10 @@ export async function push(
return [doc.value, _id];
}
export async function pull(
domainId: string, docType: number, docId: DocID,
export async function pull<K extends keyof DocumentType>(
domainId: string, docType: K, docId: DocumentType[K]['docId'],
setKey: string, contents: string[],
) {
): Promise<DocumentType[K]> {
const res = await coll.findOneAndUpdate(
{ domainId, docType, docId },
{ $pull: { [setKey]: { $in: contents } } },
+1 -2
View File
@@ -1,10 +1,9 @@
import { Dictionary } from 'lodash';
import { Collection } from 'mongodb';
import { BUILTIN_ROLES, PRIV } from './builtin';
import { DomainDoc } from '../interface';
import * as db from '../service/db';
const coll: Collection<DomainDoc> = db.collection('domain');
const coll = db.collection('domain');
const collUser = db.collection('domain.user');
export const JOIN_METHOD_NONE = 0;
+3 -3
View File
@@ -6,7 +6,7 @@ import * as file from './file';
import * as document from './document';
import * as domain from './domain';
import {
Pdoc, Pdict, ProblemDataSource, ProblemStatusDoc,
Pdoc, Pdict, ProblemDataSource, ProblemStatusDoc, NumberKeys,
} from '../interface';
import { ProblemNotFoundError } from '../error';
import * as testdataConfig from '../lib/testdataConfig';
@@ -63,7 +63,7 @@ export async function get(
if (typeof pid !== 'number') {
if (Number.isSafeInteger(parseInt(pid, 10))) pid = parseInt(pid, 10);
}
const pdoc = Number.isInteger(pid)
const pdoc = typeof pid === 'number'
? await document.get(domainId, document.TYPE_PROBLEM, pid)
: (await document.getMulti(domainId, document.TYPE_PROBLEM, { pid }).toArray())[0];
if (!pdoc) return null;
@@ -85,7 +85,7 @@ export function edit(domainId: string, _id: number, $set: Partial<Pdoc>): Promis
return document.set(domainId, document.TYPE_PROBLEM, _id, $set);
}
export function inc(domainId: string, _id: number, field: string, n: number): Promise<Pdoc> {
export function inc(domainId: string, _id: number, field: NumberKeys<Pdoc>, n: number): Promise<Pdoc> {
return document.inc(domainId, document.TYPE_PROBLEM, _id, field, n);
}
+5 -3
View File
@@ -2,11 +2,11 @@ import Lru from 'lru-cache';
import * as db from '../service/db';
const coll = db.collection('system');
const cache = new Lru({
const cache = new Lru<string, string | number>({
maxAge: 5000,
});
export async function get(_id: string) {
export async function get(_id: string): Promise<string | number> {
const res = cache.get(_id);
if (res !== undefined) return res;
const doc = await coll.findOne({ _id });
@@ -51,11 +51,13 @@ export async function set(_id: string, value: any) {
export async function inc(_id: string) {
const res = await coll.findOneAndUpdate(
{ _id },
// FIXME NumberKeys<>
// @ts-ignore
{ $inc: { value: 1 } },
{ upsert: true, returnOriginal: false },
);
cache.set(_id, res.value.value);
return res.value.value;
return res.value.value as number;
}
global.Hydro.model.system = {
+2 -2
View File
@@ -27,7 +27,7 @@ export async function enroll(domainId: string, tid: ObjectID, uid: number) {
} catch (e) {
throw new TrainingAlreadyEnrollError(tid, uid);
}
return await document.inc(domainId, document.TYPE_TRAINING, tid, 'enroll', 1);
return await document.inc(domainId, document.TYPE_TRAINING, tid, 'attend', 1);
}
export function setStatus(domainId: string, tid: ObjectID, uid: number, $set: any) {
@@ -42,7 +42,7 @@ export function add(
dag,
title,
description,
enroll: 0,
attend: 0,
});
}
+1 -1
View File
@@ -128,7 +128,7 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) {
for (const dudoc of dudocs) {
deltaudict[dudoc.uid] = dudoc.rpdelta || 0;
}
const contests = await contest.getMulti(domainId, { rated: true }, -1).sort('endAt', -1).toArray();
const contests = await contest.getMulti(domainId, { rated: true }).sort('endAt', -1).toArray();
await report({ message: `Found ${contests.length} contests in ${domainId}` });
for (const i in contests) {
const tdoc = contests[i];
+3 -2
View File
@@ -1,6 +1,7 @@
import mongodb from 'mongodb';
import mongodb, { Collection } from 'mongodb';
import * as bus from './bus';
import options from '../options';
import { Collections } from '../interface';
const opts = options();
@@ -25,7 +26,7 @@ mongodb.MongoClient.connect(mongourl, { useNewUrlParser: true, useUnifiedTopolog
});
});
export function collection(c: string) {
export function collection<K extends keyof Collections>(c: K): Collection<Collections[K]> {
if (opts.prefix) return db.collection(`${opts.prefix}.${c}`);
return db.collection(c);
}
+5 -5
View File
@@ -203,7 +203,7 @@ export function requireCsrfToken(target: any, funcName: string, obj: any) {
}
export async function prepare() {
app.keys = await system.get('session.keys');
app.keys = await system.get('session.keys') as unknown as string[];
if (argv.public) {
app.use(cache(argv.public, {
maxAge: 0,
@@ -402,7 +402,7 @@ export class Handler {
}
async init({ domainId }) {
const xff = await system.get('server.xff');
const xff = await system.get('server.xff') as string;
if (xff) this.request.ip = this.request.headers[xff.toLowerCase()];
[this.domain] = await Promise.all([
domain.get(domainId),
@@ -498,8 +498,8 @@ export class Handler {
async saveCookie() {
const expireSeconds = this.session.save
? await system.get('session.expire_seconds')
: await system.get('session.unsaved_expire_seconds');
? await system.get('session.expire_seconds') as number
: await system.get('session.unsaved_expire_seconds') as number;
if (this.session._id) {
await token.update(
this.session._id,
@@ -525,7 +525,7 @@ export class Handler {
);
}
const cookie: SetOption = {
secure: await system.get('session.secure'),
secure: !!await system.get('session.secure'),
httpOnly: false,
};
if (this.session.save) {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@hydrooj/migrate-vijos",
"version": "1.3.7",
"version": "1.3.8",
"main": "package.json",
"repository": "git@github.com:hydro-dev/Hydro.git",
"author": "undefined <masnn0@outlook.com>",
@@ -14,7 +14,7 @@
"@types/mongodb": "^3.5.27"
},
"peerDependencies": {
"hydrooj": "^2.13.0"
"hydrooj": "^2.13.5"
},
"dependencies": {
"js-yaml": "^3.14.0",
+3 -3
View File
@@ -1,6 +1,6 @@
/* eslint-disable no-await-in-loop */
import {
Rdoc, TestCase, Mdoc, Drrdoc,
Rdoc, TestCase, Mdoc, Drrdoc, TrainingNode,
} from 'hydrooj';
import fs from 'fs';
import os from 'os';
@@ -126,12 +126,12 @@ const tasks = {
},
attend: 'attend',
desc: 'description',
enroll: 'enroll',
enroll: 'attend',
rated: 'rated',
dag: {
field: 'dag',
processer: (dag) => {
const r = [];
const r: TrainingNode[] = [];
for (const t of dag) {
r.push({
_id: t._id,