mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-17 17:36:08 +08:00
template into ui
This commit is contained in:
1 parent
43ee86d34b
commit
8bb389eedd
13 files changed
+70
-206
No files matched your search
@@ -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
@@ -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
@@ -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'),
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import MarkdownIt from 'markdown-it';
|
||||
import Prism from 'prismjs';
|
||||
|
||||
// For math: $a\times b\eq 10$
|
||||
// Specific image size: 
|
||||
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,
|
||||
};
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 };
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
@@ -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
@@ -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,3 +1,4 @@
|
||||
// TODO add a set_priv interface
|
||||
import * as user from '../model/user';
|
||||
import { PRIV } from '../model/builtin';
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user