修复windows下路径的问题,添加设置项,完善翻译

This commit is contained in:
undefined committed 2020-07-16 12:27:29 +08:00
1 parent 5980080302
commit 6ad41dc2ae
29 files changed
+368 -278

No files matched your search

+30 -18
View File
@@ -1,6 +1,5 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable no-eval */
import os from 'os';
import fs from 'fs';
import path from 'path';
import yaml from 'js-yaml';
@@ -30,20 +29,18 @@ export const builtinScript = [
'blacklist', 'setSuperadmin', 'deleteUser',
];
function getFiles(folder, base = '') {
function getFiles(folder: string, base = ''): string[] {
const files = [];
const f = fs.readdirSync(folder);
for (const i of f) {
if (!i.startsWith('.')) {
if (fs.statSync(path.join(folder, i)).isDirectory()) {
files.push(...getFiles(path.join(folder, i), path.join(base, i)));
} else files.push(path.join(base, i));
}
if (fs.statSync(path.join(folder, i)).isDirectory()) {
files.push(...getFiles(path.join(folder, i), path.join(base, i)));
} else files.push(path.join(base, i));
}
return files;
return files.map((item) => item.replace(/\\/gmi, '/'));
}
export async function handler(pending, fail) {
export async function handler(pending: string[], fail: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'handler.js');
if (fs.existsSync(p) && !fail.includes(i)) {
@@ -60,7 +57,7 @@ export async function handler(pending, fail) {
}
}
export async function locale(pending, fail) {
export async function locale(pending: string[], fail: string[]) {
for (const i of pending) {
let p = path.resolve(i, 'locales');
if (!fs.existsSync(p)) p = path.resolve(i, 'locale');
@@ -82,7 +79,7 @@ export async function locale(pending, fail) {
}
}
export async function setting(pending, fail, modelSetting: typeof import('../model/setting')) {
export async function setting(pending: string[], fail: string[], modelSetting: typeof import('../model/setting')) {
const map = {
system: modelSetting.SystemSetting,
account: modelSetting.AccountSetting,
@@ -109,7 +106,7 @@ export async function setting(pending, fail, modelSetting: typeof import('../mod
}
}
export async function template(pending, fail) {
export async function template(pending: string[], fail: string[]) {
for (const i of pending) {
let p = path.resolve(i, 'templates');
if (!fs.existsSync(p)) p = path.resolve(i, 'template');
@@ -117,7 +114,9 @@ export async function template(pending, fail) {
try {
const files = getFiles(p);
for (const file of files) {
global.Hydro.template[file] = fs.readFileSync(path.resolve(p, file)).toString();
global.Hydro.ui.template[file] = fs.readFileSync(
path.resolve(p, file),
).toString();
}
console.log(`Template init: ${i}`);
} catch (e) {
@@ -128,7 +127,20 @@ export async function template(pending, fail) {
}
}
export async function model(pending, fail) {
export async function uistatic(pending: string[], fail: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'public', 'static-manifest.json');
if (fs.existsSync(p) && fs.statSync(p).isFile() && !fail.includes(i)) {
try {
Object.assign(global.Hydro.ui.manifest, eval('require')(p));
} catch (e) {
fail.push(i);
}
}
}
}
export async function model(pending: string[], fail: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'model.js');
if (fs.existsSync(p) && !fail.includes(i)) {
@@ -145,7 +157,7 @@ export async function model(pending, fail) {
}
}
export async function lib(pending, fail) {
export async function lib(pending: string[], fail: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'lib.js');
if (fs.existsSync(p) && !fail.includes(i)) {
@@ -163,7 +175,7 @@ export async function lib(pending, fail) {
}
}
export async function service(pending, fail) {
export async function service(pending: string[], fail: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'service.js');
if (fs.existsSync(p) && !fail.includes(i)) {
@@ -181,7 +193,7 @@ export async function service(pending, fail) {
}
}
export async function script(pending, fail, active) {
export async function script(pending: string[], fail: string[], active: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'script.js');
if (fs.existsSync(p) && !fail.includes(i)) {
@@ -199,7 +211,7 @@ export async function script(pending, fail, active) {
}
}
export async function wiki(pending, fail) {
export async function wiki(pending: string[], fail: string[]) {
for (const i of pending) {
const p = path.resolve(i, 'wiki');
if (fs.existsSync(p) && fs.statSync(p).isDirectory() && !fail.includes(i)) {
+15 -5
View File
@@ -4,7 +4,8 @@ import path from 'path';
import AdmZip from 'adm-zip';
import fs from 'fs-extra';
const moduleRoots = Array.from(new Set([
const moduleRoots = [];
const _moduleRoots = Array.from(new Set([
path.resolve(process.cwd(), 'node_modules', '@hydrooj'),
path.resolve(process.cwd(), 'modules'),
path.resolve(process.cwd(), 'module'),
@@ -16,17 +17,26 @@ const moduleRoots = Array.from(new Set([
path.resolve(__dirname, '.build'),
__dirname,
path.resolve(os.homedir(), '.hydro', 'module'),
// TODO add npm global path
'/usr/local/share/.config/yarn/global/node_modules/@hydrooj',
path.resolve(os.homedir(), 'AppData', 'Local', 'Yarn', 'Data', 'global', 'node_modules', '@hydrooj'),
]));
for (const moduleRoot of _moduleRoots) {
if (fs.existsSync(moduleRoot)) moduleRoots.push(moduleRoot);
}
if (process.env.debug) console.log('Module roots: ', moduleRoots);
const moduleTemp = path.resolve(os.tmpdir(), 'hydro', 'module');
const publicTemp = path.resolve(os.tmpdir(), 'hydro', 'public');
const tmp = path.resolve(os.tmpdir(), 'hydro', '__');
process.on('unhandledRejection', (e) => {
console.error(e);
process.exit(1);
});
export async function load() {
process.on('unhandledRejection', (e) => {
console.error(e);
process.exit(1);
});
fs.ensureDirSync(moduleTemp);
fs.ensureDirSync(publicTemp);
for (const moduleRoot of moduleRoots) {
+8 -2
View File
@@ -1,7 +1,7 @@
/* eslint-disable no-await-in-loop */
/* eslint-disable import/no-dynamic-require */
import {
locale, template, lib, service, model, handler, script, setting,
locale, template, lib, service, model, handler, script, setting, wiki, uistatic,
builtinLib, builtinScript, builtinHandler, builtinModel,
} from './common';
@@ -13,7 +13,12 @@ export async function load() {
require('../utils');
require('../error');
require('../options');
await Promise.all([locale(pending, fail), template(pending, fail)]);
await Promise.all([
locale(pending, fail),
template(pending, fail),
uistatic(pending, fail),
wiki(pending, fail),
]);
const bus = require('../service/bus');
await new Promise((resolve) => {
const h = () => {
@@ -51,6 +56,7 @@ export async function load() {
}
}
for (const i of builtinScript) require(`../script/${i}`);
await wiki(pending, fail);
await script(pending, fail, active);
for (const postInit of global.Hydro.postInit) {
await postInit();
+1 -1
View File
@@ -280,7 +280,7 @@ class ContestProblemSubmitHandler extends ContestProblemHandler {
ttype: document.TYPE_CONTEST,
lang,
code,
});
}, true);
await contest.updateStatus(domainId, this.tdoc.docId, this.user._id, rid, this.pdoc.docId);
if (!this.canShowRecord(this.tdoc)) {
this.response.body = { tid };
+5 -6
View File
@@ -1,9 +1,7 @@
import { ObjectID } from 'mongodb';
import {
VerifyPasswordError, UserAlreadyExistError, InvalidTokenError,
NotFoundError,
UserNotFoundError,
PermissionError,
NotFoundError, UserNotFoundError, PermissionError,
} from '../error';
import * as bus from '../service/bus';
import {
@@ -321,7 +319,7 @@ class HomeMessagesHandler extends Handler {
const udoc = await user.getById('system', uid);
if (!udoc) throw new UserNotFoundError(uid);
if (udoc.gravatar) udoc.gravatar = misc.gravatar(udoc.gravatar);
const mdoc = await message.send(this.user._id, uid, content);
const mdoc = await message.send(this.user._id, uid, content, message.FLAG_UNREAD);
// TODO(twd2): improve here: projection
if (this.user._id !== uid) {
bus.publish(`user_message-${uid}`, { mdoc, udoc });
@@ -340,8 +338,9 @@ class HomeMessagesHandler extends Handler {
@param('messageId', Types.ObjectID)
async postRead(domainId: string, messageId: ObjectID) {
const msg = await message.get(messageId);
if ([msg.from, msg.to].includes(this.user._id)) await message.setFlag(messageId, message.FLAG_UNREAD);
else throw new PermissionError();
if ([msg.from, msg.to].includes(this.user._id)) {
await message.setFlag(messageId, message.FLAG_UNREAD);
} else throw new PermissionError();
this.back();
}
}
+1 -1
View File
@@ -182,7 +182,7 @@ class HomeworkDetailProblemSubmitHandler extends HomeworkDetailProblemHandler {
if (!this.tdoc.pids.includes(pid)) throw new ProblemNotFoundError(domainId, pid);
const rid = await record.add(domainId, {
pid, lang, code, uid: this.user._id, tid, hidden: true, ttype: document.TYPE_HOMEWORK,
});
}, true);
await contest.updateStatus(domainId, tid, this.user._id,
rid, pid, false, 0, document.TYPE_HOMEWORK);
this.response.body = { tid, rid };
+1 -1
View File
@@ -126,7 +126,7 @@ class PretestHandler extends Handler {
lang,
code,
hidden: true,
});
}, true);
this.response.body = { rid };
this.response.redirect = this.url('record_detail', { rid });
}
+8 -5
View File
@@ -6,7 +6,7 @@ import * as system from '../model/system';
import { STATUS, PRIV } from '../model/builtin';
import * as record from '../model/record';
import {
Route, Connection, Handler, ConnectionHandler,
Route, Connection, Handler, ConnectionHandler, param, Types,
} from '../service/server';
import { validate } from '../lib/validator';
import * as hpm from '../lib/hpm';
@@ -101,18 +101,21 @@ class SystemScriptHandler extends SystemHandler {
this.response.body.path.push(['manage_script', null]);
}
async post({ domainId, id, args = '{}' }) {
@param('id', Types.String)
@param('args', Types.String, true)
async post(domainId: string, id: string, args = '{}') {
if (!global.Hydro.script[id]) throw new ValidationError('id');
args = JSON.parse(args);
validate(global.Hydro.script[id].validate, args);
const rid = await record.add(domainId, {
pid: id,
pid: 1,
uid: this.user._id,
lang: null,
code: null,
status: STATUS.STATUS_JUDGING,
hidden: true,
}, false);
judge.next({ domainId, rid, message: `Running script: ${id}` });
async function report(data) {
judge.next({ domainId, rid, ...data });
}
@@ -125,13 +128,13 @@ class SystemScriptHandler extends SystemHandler {
domainId,
rid,
status: STATUS.STATUS_ACCEPTED,
judge_text: ret,
judge_text: ret.toString(),
judger: 1,
time_ms: time,
memory_kb: 0,
});
})
.catch((err) => {
.catch((err: Error) => {
const time = new Date().getTime() - start;
judge.end({
domainId,
+1 -1
View File
@@ -234,7 +234,7 @@ class ProblemPretestHandler extends ProblemDetailHandler {
this.limitRate('add_record', 60, 100);
const rid = await record.add(domainId, {
uid: this.user._id, lang, code, pid: this.pdoc.docId, input,
});
}, true);
await record.judge(domainId, rid);
this.response.body = { rid };
}
+5 -2
View File
@@ -259,8 +259,11 @@ declare global {
},
stat: any,
wiki: { [category: string]: { [page: string]: any } },
template: { [key: string]: string },
ui: any,
ui: {
manifest: Dict<string>,
nav: any,
template: { [key: string]: string },
},
error: typeof import('./error'),
locales: Dict<Dict<string>>,
postInit: Function[],
+2 -2
View File
@@ -1,4 +1,4 @@
import yaml from 'js-yaml';
import serialize from 'serialize-javascript';
function wrap(func: Function, type: string) {
return (...args: any) => {
@@ -12,7 +12,7 @@ function wrap(func: Function, type: string) {
args[i] = `${args[i].message}\n${args[i].stack}`;
}
}
global.Hydro.model.message.send(1, 1, yaml.dump({ time, type, args }, {}));
global.Hydro.model.message.send(1, 1, serialize({ time, type, args }), 0);
} catch (e) {
func(e.message);
}
+14 -9
View File
@@ -3,16 +3,15 @@ import path from 'path';
import serialize from 'serialize-javascript';
import nunjucks from 'nunjucks';
import * as markdown from './markdown';
import { STATUS, PERM, PRIV } from '../model/builtin';
import * as misc from './misc';
class Loader extends nunjucks.Loader {
// eslint-disable-next-line class-methods-use-this
getSource(name: string) {
if (!process.env.debug) {
if (!global.Hydro.template[name]) throw new Error(`Cannot get template ${name}`);
if (!global.Hydro.ui.template[name]) throw new Error(`Cannot get template ${name}`);
return {
src: global.Hydro.template[name],
src: global.Hydro.ui.template[name],
path: name,
noCache: false,
};
@@ -47,18 +46,24 @@ class Nunjucks extends nunjucks.Environment {
}
const env = new Nunjucks();
export function render(name: string, state: any): Promise<string> {
return new Promise((resolve, reject) => {
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: (str) => `/${str}`,
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: PERM,
PRIV,
STATUS,
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,
+5 -2
View File
@@ -19,8 +19,11 @@ global.Hydro = {
// @ts-ignore
lib: {},
wiki: {},
template: {},
ui: {},
ui: {
manifest: {},
nav: [],
template: {},
},
// @ts-ignore
error: {},
locales: {},
+8 -5
View File
@@ -5,14 +5,17 @@ import * as db from '../service/db';
const coll = db.collection('message');
export const FLAG_UNREAD = 1;
export const FLAG_NOTIFICATION = 2;
export const FLAG_ALERT = 2;
export async function send(from: number, to: number, content: string, flag: number): Promise<Mdoc> {
export async function send(
from: number, to: number,
content: string, flag: number = FLAG_UNREAD,
): Promise<Mdoc> {
const res = await coll.insertOne({
from, to, content, flag: FLAG_UNREAD | flag,
from, to, content, flag,
});
return {
from, to, content, _id: res.insertedId, flag: FLAG_UNREAD | flag,
from, to, content, _id: res.insertedId, flag,
};
}
@@ -60,7 +63,7 @@ export function ensureIndexes() {
global.Hydro.model.message = {
FLAG_UNREAD,
FLAG_NOTIFICATION,
FLAG_ALERT,
count,
get,
+1 -1
View File
@@ -46,7 +46,7 @@ export interface JudgeTask {
type?: string,
}
export async function add(domainId: string, data: RdocBase, addTask = true) {
export async function add(domainId: string, data: RdocBase, addTask: boolean) {
_.defaults(data, {
status: STATUS.STATUS_WAITING,
type: 'judge',
+3 -1
View File
@@ -117,13 +117,15 @@ SystemSetting(
Setting('setting_server', 'server.hostname', null, null, 'text', 'Server Hostname'),
Setting('setting_server', 'server.host', null, null, 'text', 'Server Host'),
Setting('setting_server', 'server.url', null, null, 'text', 'Server BaseURL'),
Setting('setting_server', 'server.cdn', null, '/', 'text', 'CDN Prefix', 'Ends with /'),
Setting('setting_server', 'server.port', null, 8888, 'number', 'Server Port'),
Setting('setting_server', 'server.xff', null, null, 'text', 'IP Header'),
Setting('setting_server', 'server.xff', null, null, 'text', 'IP Header', 'e.g. x-forwarded-for (lowercase)'),
Setting('setting_server', 'server.log', null, false, 'boolean', 'Disable Access Log'),
Setting('setting_session', 'session.keys', null, [String.random(32)], 'text', 'session.keys', null, FLAG_DISABLED | FLAG_HIDDEN),
Setting('setting_session', 'session.secure', null, false, 'boolean', 'session.secure'),
Setting('setting_session', 'session.saved_expire_seconds', null, 3600 * 24 * 30, 'number', 'Saved session expire seconds'),
Setting('setting_session', 'session.unsaved_expire_seconds', null, 3600 * 3, 'number', 'Unsaved session expire seconds'),
Setting('setting_ui', 'ui.name', null, 'Hydro', 'text', 'Site Name'),
Setting('setting_ui', 'ui.header_logo', null, '/components/header/header-logo.png', 'text', 'Header Logo'),
Setting('setting_ui', 'ui.header_logo_2x', null, '/components/header/header-logo@2x.png', 'text', 'Header Logo@2x'),
Setting('setting_ui', 'ui.header_background', null, '/components/header/header-background.png', 'text', 'Header Background'),
+33 -12
View File
@@ -1,15 +1,31 @@
import { FindOneAndUpdateOption } from 'mongodb';
import Lru from 'lru-cache';
import * as db from '../service/db';
const coll = db.collection('system');
const cache = new Lru({
maxAge: 5000,
});
export async function get(_id: string) {
const res = cache.get(_id);
if (res !== undefined) return res;
const doc = await coll.findOne({ _id });
if (doc) return doc.value;
if (doc) {
cache.set(_id, doc.value);
return doc.value;
}
return null;
}
export async function getMany(keys: string[]): Promise<any[]> {
const r = [];
let success = true;
for (const key of keys) {
const res = cache.get(key);
if (res !== undefined) r.push(res);
else success = false;
}
if (success) return r;
const docs = await coll.find({ _id: { $in: keys } }).toArray();
const dict = {};
const res = [];
@@ -22,24 +38,29 @@ export async function getMany(keys: string[]): Promise<any[]> {
return res;
}
export async function update(_id: string, operation: any, config: FindOneAndUpdateOption) {
await coll.findOneAndUpdate({ _id }, operation, config);
return get(_id);
}
export async function set(_id: string, value: any) {
await coll.findOneAndUpdate({ _id }, { $set: { value } }, { upsert: true });
return get(_id);
cache.set(_id, value);
const res = await coll.findOneAndUpdate(
{ _id },
{ $set: { value } },
{ upsert: true, returnOriginal: false },
);
return res.value.value;
}
export function inc(field: string) {
return update(field, { $inc: { value: 1 } }, { upsert: true });
export async function inc(_id: string) {
const res = await coll.findOneAndUpdate(
{ _id },
{ $inc: { value: 1 } },
{ upsert: true, returnOriginal: false },
);
cache.set(_id, res.value.value);
return res.value.value;
}
global.Hydro.model.system = {
get,
getMany,
update,
inc,
set,
};