template into ui

This commit is contained in:
undefined committed 2020-07-31 13:09:08 +08:00
1 parent 43ee86d34b
commit 8bb389eedd
13 files changed
+70 -206

No files matched your search

-11
View File
@@ -5,7 +5,6 @@ import { PRIV } from '../model/builtin';
import * as system from '../model/system';
import * as file from '../model/file';
import * as user from '../model/user';
import * as markdown from '../lib/markdown';
import * as db from '../service/db';
import {
Route, Handler, Types, param, multipart,
@@ -103,15 +102,6 @@ class SwitchLanguageHandler extends Handler {
}
}
class MarkdownHandler extends Handler {
async post({ text, html = false, inline = false }) {
this.response.body = inline
? markdown.renderInline(text, html)
: markdown.render(text, html);
this.response.type = 'text/html';
}
}
class SockToken extends Handler {
async get() {
this.response.body = { token: this.UIContext.token };
@@ -153,7 +143,6 @@ export async function apply() {
Route('status', '/status', StatusHandler);
Route('status_update', '/status/update', StatusUpdateHandler);
Route('switch_language', '/language/:lang', SwitchLanguageHandler);
Route('markdown', '/markdown', MarkdownHandler);
Route('token', '/token', SockToken);
Route('ui', '/extra.css', UiSettingsHandler);
}
+11 -6
View File
@@ -238,7 +238,9 @@ class OauthHandler extends Handler {
}
class OauthCallbackHandler extends Handler {
async github({ state, code }) {
@param('state', Types.String)
@param('code', Types.String)
async github(domainId: string, state: string, code: string) {
const [appid, secret, url, proxy, s] = await Promise.all([
system.get('oauth.githubappid'),
system.get('oauth.githubsecret'),
@@ -272,13 +274,14 @@ class OauthCallbackHandler extends Handler {
uname: [userInfo.body.name, userInfo.body.login],
};
this.response.redirect = s.redirect;
await token.del(s, token.TYPE_OAUTH);
await token.del(s._id, token.TYPE_OAUTH);
return ret;
}
async google({
code, error, state,
}) {
@param('state', Types.String)
@param('code', Types.String)
@param('error', Types.String)
async google(domainId: string, state: string, code: string, error: string) {
if (error) throw new UserFacingError(error);
const [
[appid, secret, url, proxy],
@@ -308,9 +311,11 @@ class OauthCallbackHandler extends Handler {
};
}
async get(args) {
async get(args: any) {
let r;
// @ts-ignore
if (args.type === 'github') r = await this.github(args);
// @ts-ignore
else if (args.type === 'google') r = await this.google(args);
else throw new UserFacingError('Oauth type');
const udoc = await user.getByEmail('system', r.email);
+9 -2
View File
@@ -277,6 +277,15 @@ export interface Drrdoc {
ip: string,
}
export interface TokenDoc {
_id: string,
tokenType: number,
createAt: Date,
updateAt: Date,
expireAt: Date,
[key: string]: any,
}
export interface ContestStat extends Dictionary<any> {
detail: any,
}
@@ -372,7 +381,6 @@ declare global {
jwt: typeof import('./lib/jwt'),
logger: typeof import('./lib/logger').default,
mail: typeof import('./lib/mail'),
markdown: typeof import('./lib/markdown'),
md5: typeof import('./lib/md5').default,
misc: typeof import('./lib/misc'),
nav: typeof import('./lib/nav').default,
@@ -382,7 +390,6 @@ declare global {
testdataConfig: typeof import('./lib/testdataConfig'),
sha1: typeof import('./lib/sha1').default,
sysinfo: typeof import('./lib/sysinfo'),
template: typeof import('./lib/template'),
'testdata.convert.ini': typeof import('./lib/testdata.convert.ini').default,
useragent: typeof import('./lib/useragent'),
validator: typeof import('./lib/validator'),
-67
View File
@@ -1,67 +0,0 @@
import MarkdownIt from 'markdown-it';
import Prism from 'prismjs';
// For math: $a\times b\eq 10$
// Specific image size: ![image](image.png =100x100)
import Imsize from 'markdown-it-imsize';
/* Footnote support.
Here is a footnote reference,[^1] and another.[^longnote]
[^1]: Here is the footnote.
[^longnote]: Here's one with multiple blocks.
Subsequent paragraphs are indented to show that they
belong to the previous footnote.
*/
import Footnote from 'markdown-it-footnote';
// ==Highlight==
import Mark from 'markdown-it-mark';
import TOC from 'markdown-it-table-of-contents';
import Anchor from 'markdown-it-anchor';
require('prismjs/components/index');
type Plugin = MarkdownIt.PluginSimple | MarkdownIt.PluginWithOptions | MarkdownIt.PluginWithParams;
class Markdown extends MarkdownIt {
constructor() {
super({
linkify: true,
highlight(str, lang) {
if (lang && Prism.languages[lang]) {
try {
return Prism.highlight(str, Prism.languages[lang], lang);
} catch (__) { } // eslint-disable-line no-empty
}
return '';
},
});
this.linkify.tlds('.py', false);
this.use(Imsize);
this.use(Footnote);
this.use(Mark);
this.use(Anchor);
this.use(TOC);
}
}
export const md = new Markdown();
export function plugin(func: Plugin, ...args: any[]) {
md.use(func, ...args);
}
export function render(text: string, html = false) {
md.set({ html: !!html });
return md.render(text);
}
export function renderInline(text: string, html = false) {
md.set({ html: !!html });
return md.renderInline(text);
}
global.Hydro.lib.markdown = {
md, plugin, render, renderInline,
};
+1 -1
View File
@@ -3,7 +3,7 @@ import { ValidationError } from '../error';
export default async function paginate(
cursor: Cursor, page: number, pageSize: number,
): Promise<[any[], number, number]> {
): Promise<[any[]/* docs */, number/* numPages */, number/* count */]> {
if (page <= 0) throw new ValidationError('page');
const [count, pageDocs] = await Promise.all([
cursor.count(),
-78
View File
@@ -1,78 +0,0 @@
import fs from 'fs';
import path from 'path';
import serialize from 'serialize-javascript';
import nunjucks from 'nunjucks';
import html2text from 'html-to-text';
import { argv } from 'yargs';
import * as markdown from './markdown';
import * as misc from './misc';
class Loader extends nunjucks.Loader {
// eslint-disable-next-line class-methods-use-this
getSource(name: string) {
if (!argv.template) {
if (!global.Hydro.ui.template[name]) throw new Error(`Cannot get template ${name}`);
return {
src: global.Hydro.ui.template[name],
path: name,
noCache: false,
};
}
let fullpath = null;
const p = path.resolve(process.cwd(), argv.template as string, name);
if (fs.existsSync(p)) fullpath = p;
if (!fullpath) throw new Error(`Cannot get template ${name}`);
return {
src: fs.readFileSync(fullpath, 'utf-8').toString(),
path: fullpath,
noCache: true,
};
}
}
class Nunjucks extends nunjucks.Environment {
constructor() {
super(new Loader(), { autoescape: true, trimBlocks: true });
this.addFilter('json', (self) => JSON.stringify(self), false);
this.addFilter('serialize', (self, ignoreFunction = true) => serialize(self, { ignoreFunction }));
this.addFilter('assign', (self, data) => Object.assign(self, data));
this.addFilter('markdown', (self, html = false) => markdown.render(self, html));
this.addFilter('ansi', (self) => misc.ansiToHtml(self));
this.addFilter('inline', (self) => html2text.fromString(self).replace(/\n/gmi, ' '));
this.addFilter('base64_encode', (s) => Buffer.from(s).toString('base64'));
this.addFilter('bitand', (self, val) => self & val);
}
}
const env = new Nunjucks();
export async function render(name: string, state: any): Promise<string> {
const cdnPrefix = await global.Hydro.model.system.get('server.cdn');
return await new Promise((resolve, reject) => {
env.render(name, {
page_name: name.split('.')[0],
...state,
typeof: (o) => typeof o,
static_url: (assetName: string) => {
if (global.Hydro.ui.manifest[assetName]) {
return `${cdnPrefix}${global.Hydro.ui.manifest[assetName]}`;
}
return `${cdnPrefix}${assetName}`;
},
datetimeSpan: misc.datetimeSpan,
paginate: misc.paginate,
perm: global.Hydro.model.builtin.PERM,
PRIV: global.Hydro.model.builtin.PRIV,
STATUS: global.Hydro.model.builtin.STATUS,
size: misc.size,
gravatar: misc.gravatar,
model: global.Hydro.model,
Context: global.Hydro.ui,
isIE: (str: string) => str.includes('MSIE') || str.includes('rv:11.0'),
}, (err, res) => {
if (err) reject(err);
else resolve(res);
});
});
}
global.Hydro.lib.template = { render };
+3 -2
View File
@@ -6,7 +6,6 @@ import * as problem from './problem';
import {
Rdoc, TestCase, PretestConfig, ContestInfo, ProblemConfig,
} from '../interface';
import { RecordNotFoundError } from '../error';
import * as db from '../service/db';
const coll = db.collection('record');
@@ -48,7 +47,9 @@ export interface JudgeTask {
}
export async function get(domainId: string, _id: ObjectID): Promise<Rdoc | null> {
return await coll.findOne({ domainId, _id });
const res = await coll.findOne({ _id });
if (res && res.domainId === domainId) return res;
return null;
}
export async function judge(domainId: string, rid: ObjectID) {
+8 -6
View File
@@ -1,3 +1,4 @@
import { TokenDoc } from '../interface';
import * as db from '../service/db';
const coll = db.collection('token');
@@ -16,7 +17,9 @@ function ensureIndexes() {
]);
}
export async function add(tokenType: number, expireSeconds: number, data: any) {
export async function add(
tokenType: number, expireSeconds: number, data: any,
): Promise<[string, TokenDoc]> {
const now = new Date();
const str = String.random(32);
const res = await coll.insertOne({
@@ -30,9 +33,8 @@ export async function add(tokenType: number, expireSeconds: number, data: any) {
return [str, res.ops[0]];
}
export async function get(tokenId: string, tokenType: number) {
const res = await coll.findOne({ _id: tokenId, tokenType });
return res;
export async function get(tokenId: string, tokenType: number): Promise<TokenDoc | null> {
return await coll.findOne({ _id: tokenId, tokenType });
}
export async function update(
@@ -73,11 +75,11 @@ export async function createOrUpdate(
}
export function getSessionListByUid(uid: number) {
return coll.find({ uid, tokenType: this.TYPE_SESSION }).sort('updateAt', -1).toArray();
return coll.find({ uid, tokenType: TYPE_SESSION }).sort('updateAt', -1).toArray();
}
export function getMostRecentSessionByUid(uid: number) {
return coll.findOne({ uid, tokenType: this.TYPE_SESSION }, { sort: { updateAt: -1 } });
return coll.findOne({ uid, tokenType: TYPE_SESSION }, { sort: { updateAt: -1 } });
}
export function delByUid(uid: number) {
+2 -2
View File
@@ -199,7 +199,7 @@ export async function create(
return uid;
}
export function getMulti(params) {
export function getMulti(params: any) {
return coll.find(params);
}
@@ -210,7 +210,7 @@ export async function getPrefixList(prefix: string, limit = 50) {
return udocs;
}
export async function setPriv(uid: number, priv: number) {
export async function setPriv(uid: number, priv: number): Promise<Udoc | null> {
const udoc = await coll.findOneAndUpdate(
{ _id: uid },
{ $set: { priv } },
+32 -27
View File
@@ -1,5 +1,5 @@
/* eslint-disable no-await-in-loop */
import { NumericDictionary, filter } from 'lodash';
import { NumericDictionary } from 'lodash';
import { ObjectID } from 'mongodb';
import { Tdoc, Pdoc } from '../interface';
import * as domain from '../model/domain';
@@ -8,23 +8,12 @@ import * as problem from '../model/problem';
import * as record from '../model/record';
import { STATUS } from '../model/builtin';
import rating from '../lib/rating';
import paginate from '../lib/paginate';
export const description = 'Calculate rp of a domain, or all domains';
type ND = NumericDictionary<number>
function calc(udict: ND, rankedDocs: [number, number][]) {
const users = [];
for (const [rk, uid] of rankedDocs) {
users.push({ uid, rank: rk, old: udict[uid] || 1500 });
}
// FIXME sum(rating.new) always less than sum(rating.old)
const rated = rating(users);
for (const udoc of rated) {
udict[udoc.uid] = udoc.new;
}
}
async function runProblem(pdoc: Pdoc, udict: ND): Promise<void>
async function runProblem(domainId: string, pid: number, udict: ND): Promise<void>
async function runProblem(...arg: any[]) {
@@ -32,17 +21,31 @@ async function runProblem(...arg: any[]) {
? await problem.get(arg[0], arg[1], -1)
: arg[0];
const udict: ND = (typeof arg[0] === 'string') ? arg[2] : arg[1];
const psdocs = await problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, rid: { $ne: null } },
).toArray();
if (!psdocs.length) return;
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true);
const nAccept = filter(psdocs, (psdoc) => psdoc.status === STATUS.STATUS_ACCEPTED).length;
const [, nPages] = await paginate(
problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, rid: { $ne: null } },
),
1,
100,
);
const nAccept = await problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, status: STATUS.STATUS_ACCEPTED },
).count();
const p = (pdoc.difficulty || 5) / (Math.sqrt(Math.sqrt(nAccept)) + 1);
for (const psdoc of psdocs) {
if (rdict[psdoc.rid]) {
const rp = rdict[psdoc.rid].score * p;
udict[psdoc.uid] = (udict[psdoc.uid] || 1500) + rp;
for (let page = 1; page <= nPages; page++) {
const [psdocs] = await paginate(
problem.getMultiStatus(
pdoc.domainId, { docId: pdoc.docId, rid: { $ne: null } },
),
page,
100,
);
const rdict = await record.getList(pdoc.domainId, psdocs.map((psdoc) => psdoc.rid), true);
for (const psdoc of psdocs) {
if (rdict[psdoc.rid]) {
const rp = rdict[psdoc.rid].score * p;
udict[psdoc.uid] = (udict[psdoc.uid] || 1500) + rp;
}
}
}
}
@@ -62,11 +65,13 @@ async function runContest(...arg: any[]) {
.sort(contest.RULES[tdoc.rule].statusSort).toArray();
if (!tsdocs.length) return;
const rankedTsdocs = contest.RULES[tdoc.rule].rank(tsdocs);
const ranked = [];
const users = [];
for (const result of rankedTsdocs) {
ranked.push([result[0], result[1].uid]);
users.push({ uid: result[1].uid, rank: result[0], old: udict[result[1].uid] || 1500 });
}
calc(udict, ranked);
// FIXME sum(rating.new) always less than sum(rating.old)
const rated = rating(users);
for (const udoc of rated) udict[udoc.uid] = udoc.new;
await report({
case: {
status: STATUS.STATUS_ACCEPTED,
@@ -79,7 +84,6 @@ async function runContest(...arg: any[]) {
}
async function runInDomain(domainId: string, isSub: boolean, report: Function) {
await domain.setMultiUserInDomain(domainId, {}, { rp: 1500 });
const udict: ND = {};
const contests = await contest.getMulti(domainId, { rated: true }, -1).sort('endAt', -1).toArray();
await report({ message: `Found ${contests.length} contests in ${domainId}` });
@@ -104,6 +108,7 @@ async function runInDomain(domainId: string, isSub: boolean, report: Function) {
});
}
}
await domain.setMultiUserInDomain(domainId, {}, { rp: 1500 });
const tasks = [];
for (const uid in udict) {
tasks.push(domain.setUserInDomain(domainId, parseInt(uid, 10), { rp: udict[uid] }));
+1
View File
@@ -1,3 +1,4 @@
// TODO add a set_priv interface
import * as user from '../model/user';
import { PRIV } from '../model/builtin';
+2 -3
View File
@@ -22,7 +22,6 @@ import {
CsrfTokenError, InvalidOperationError, MethodNotAllowedError,
NotFoundError, HydroError,
} from '../error';
import { render } from '../lib/template';
import hash from '../lib/hash.hydro';
import * as misc from '../lib/misc';
import * as user from '../model/user';
@@ -276,7 +275,7 @@ export class Handler {
gravatar: misc.gravatar(this.user.gravatar || '', 128),
perm: this.user.perm.toString(),
};
const res = await render(name, {
const res = await global.Hydro.lib.template.render(name, {
handler: this,
UserContext,
url: this.url.bind(this),
@@ -615,7 +614,7 @@ export class ConnectionHandler {
}
async renderHTML(name: string, context: any): Promise<string> {
const res = await render(name, Object.assign(context, {
const res = await global.Hydro.lib.template.render(name, Object.assign(context, {
handler: this,
url: this.url.bind(this),
_: this.translate.bind(this),
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hydrooj",
"version": "2.11.1",
"version": "2.11.2",
"main": "dist/loader.js",
"bin": "bin/hydrooj.js",
"repository": "https://github.com/hydro-dev/Hydro.git",