mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-19 17:38:19 +08:00
Merge branch 'master' into events
This commit is contained in:
57 files changed
+381
-229
No files matched your search
@@ -71,6 +71,17 @@ export class Context extends cordis.Context {
|
||||
super();
|
||||
this.plugin(ApiMixin);
|
||||
this.plugin(TimerService);
|
||||
this.plugin(LoggerService);
|
||||
this.plugin(LoggerService, {
|
||||
console: {
|
||||
showDiff: false,
|
||||
showTime: 'dd hh:mm:ss',
|
||||
label: {
|
||||
align: 'right',
|
||||
width: 9,
|
||||
margin: 1,
|
||||
},
|
||||
levels: { default: process.env.DEV ? 3 : 2 },
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { ObjectId } from 'mongodb';
|
||||
import { Context } from '../context';
|
||||
import { load as loadOptions } from '../options';
|
||||
import { MongoService } from '../service/db';
|
||||
import { ConfigService } from '../settings';
|
||||
import { SettingService } from '../settings';
|
||||
import {
|
||||
addon, builtinModel, model, service,
|
||||
} from './common';
|
||||
@@ -110,10 +110,10 @@ export async function load(ctx: Context) {
|
||||
const pending = global.addons;
|
||||
const fail = [];
|
||||
await ctx.plugin(MongoService, loadOptions() || {});
|
||||
await ctx.plugin(ConfigService);
|
||||
await ctx.plugin(SettingService);
|
||||
await require('../model/system').runConfig();
|
||||
ctx = await new Promise((resolve) => {
|
||||
ctx.inject(['loader', 'config', 'db'], (c) => {
|
||||
ctx.inject(['loader', 'setting', 'db'], (c) => {
|
||||
resolve(c);
|
||||
});
|
||||
});
|
||||
@@ -127,7 +127,7 @@ export async function load(ctx: Context) {
|
||||
await builtinModel(ctx);
|
||||
await model(pending, fail, ctx);
|
||||
ctx = await new Promise((resolve) => {
|
||||
ctx.inject(['server', 'setting', 'worker'], (c) => {
|
||||
ctx.inject(['server', 'worker'], (c) => {
|
||||
resolve(c);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Context } from '../context';
|
||||
import { Logger } from '../logger';
|
||||
import { load } from '../options';
|
||||
import { MongoService } from '../service/db';
|
||||
import { ConfigService } from '../settings';
|
||||
import { SettingService } from '../settings';
|
||||
import {
|
||||
addon, builtinModel, locale, model, service,
|
||||
} from './common';
|
||||
@@ -30,11 +30,11 @@ export async function apply(ctx: Context) {
|
||||
const fail = [];
|
||||
await locale(pending, fail);
|
||||
await ctx.plugin(MongoService, load() || {});
|
||||
await ctx.plugin(ConfigService);
|
||||
await ctx.plugin(SettingService);
|
||||
const modelSystem = require('../model/system');
|
||||
await modelSystem.runConfig();
|
||||
ctx = await new Promise((resolve) => {
|
||||
ctx.inject(['loader', 'config', 'db'], (c) => {
|
||||
ctx.inject(['loader', 'setting', 'db'], (c) => {
|
||||
resolve(c);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -576,7 +576,7 @@ export class ContestBalloonHandler extends ContestManagementBaseHandler {
|
||||
const bdocs = await contest.getMultiBalloon(domainId, tid, {
|
||||
...todo ? { sent: { $exists: false } } : {},
|
||||
...(!this.tdoc.lockAt || this.user.hasPerm(PERM.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD))
|
||||
? {} : { _id: { $lt: this.tdoc.lockAt } },
|
||||
? {} : { _id: { $lt: Time.getObjectID(this.tdoc.lockAt) } },
|
||||
}).sort({ _id: -1 }).toArray();
|
||||
const uids = bdocs.map((i) => i.uid).concat(bdocs.filter((i) => i.sent).map((i) => i.sent));
|
||||
this.response.body = {
|
||||
@@ -743,7 +743,7 @@ export async function apply(ctx: Context) {
|
||||
await Promise.all(tasks);
|
||||
});
|
||||
ctx.plugin(ScoreboardService);
|
||||
ctx.inject(['scoreboard'], ({ Route, scoreboard }) => {
|
||||
await ctx.inject(['scoreboard'], ({ Route, scoreboard }) => {
|
||||
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
|
||||
Route('contest_scoreboard_view', '/contest/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
|
||||
scoreboard.addView('default', 'Default', { tdoc: 'tdoc', groups: 'groups', realtime: Types.Boolean }, {
|
||||
|
||||
@@ -214,6 +214,7 @@ class DomainPermissionHandler extends ManageHandler {
|
||||
async post({ domainId }) {
|
||||
const roles = {};
|
||||
for (const role in this.request.body) {
|
||||
if (role === 'root') continue; // root role is not editable
|
||||
const perms = this.request.body[role] instanceof Array
|
||||
? this.request.body[role]
|
||||
: [this.request.body[role]];
|
||||
@@ -460,7 +461,7 @@ export async function apply(ctx: Context) {
|
||||
ctx.Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler);
|
||||
ctx.Route('domain_join', '/domain/join', DomainJoinHandler, PRIV.PRIV_USER_PROFILE);
|
||||
ctx.Route('domain_search', '/domain/search', DomainSearchHandler, PRIV.PRIV_USER_PROFILE);
|
||||
ctx.inject(['api'], ({ api }) => {
|
||||
await ctx.inject(['api'], ({ api }) => {
|
||||
api.provide(DomainApi);
|
||||
});
|
||||
}
|
||||
@@ -314,7 +314,7 @@ export async function apply(ctx) {
|
||||
ctx.Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler);
|
||||
ctx.Route('homework_files', '/homework/:tid/file', HomeworkFilesHandler, PERM.PERM_VIEW_HOMEWORK);
|
||||
ctx.Route('homework_file_download', '/homework/:tid/file/:filename', ContestFileDownloadHandler, PERM.PERM_VIEW_HOMEWORK);
|
||||
ctx.inject(['scoreboard'], ({ Route }) => {
|
||||
await ctx.inject(['scoreboard'], ({ Route }) => {
|
||||
Route('homework_scoreboard', '/homework/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||
Route('homework_scoreboard_view', '/homework/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
|
||||
});
|
||||
|
||||
@@ -182,7 +182,7 @@ class SystemConfigHandler extends SystemHandler {
|
||||
@requireSudo
|
||||
async get() {
|
||||
this.response.template = 'manage_config.html';
|
||||
let value;
|
||||
let value = this.ctx.setting.configSource;
|
||||
|
||||
const processNode = (node: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
||||
if (['union', 'intersect'].includes(schema.type)) {
|
||||
@@ -198,23 +198,20 @@ class SystemConfigHandler extends SystemHandler {
|
||||
};
|
||||
|
||||
try {
|
||||
try {
|
||||
value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource));
|
||||
} catch (e) {
|
||||
value = yaml.load(this.ctx.config.configSource);
|
||||
}
|
||||
for (const schema of this.ctx.config.settings) processNode(value, schema);
|
||||
const temp = yaml.load(this.ctx.setting.configSource);
|
||||
for (const schema of this.ctx.setting.settings) processNode(temp, schema);
|
||||
value = yaml.dump(temp);
|
||||
} catch (e) { }
|
||||
this.response.body = {
|
||||
schema: Schema.intersect(this.ctx.config.settings).toJSON(),
|
||||
value: yaml.dump(value),
|
||||
schema: Schema.intersect(this.ctx.setting.settings).toJSON(),
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
@requireSudo
|
||||
@param('value', Types.String)
|
||||
async post({ }, value: string) {
|
||||
const oldConfig = yaml.load(this.ctx.config.configSource);
|
||||
const oldConfig = yaml.load(this.ctx.setting.configSource);
|
||||
let config;
|
||||
const processNode = (node: any, old: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
|
||||
if (['union', 'intersect'].includes(schema.type)) {
|
||||
@@ -231,11 +228,11 @@ class SystemConfigHandler extends SystemHandler {
|
||||
|
||||
try {
|
||||
config = yaml.load(value);
|
||||
for (const schema of this.ctx.config.settings) processNode(config, oldConfig, schema, null, '');
|
||||
for (const schema of this.ctx.setting.settings) processNode(config, oldConfig, schema, null, '');
|
||||
} catch (e) {
|
||||
throw new ValidationError('value', '', e.message);
|
||||
}
|
||||
await this.ctx.config.saveConfig(config);
|
||||
await this.ctx.setting.saveConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +354,7 @@ class SystemUserPrivHandler extends SystemHandler {
|
||||
}
|
||||
}
|
||||
|
||||
export const inject = ['config', 'check'];
|
||||
export const inject = ['setting', 'check'];
|
||||
export async function apply(ctx) {
|
||||
ctx.Route('manage', '/manage', SystemMainHandler);
|
||||
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
|
||||
|
||||
@@ -384,10 +384,10 @@ export class ProblemDetailHandler extends ContestDetailBaseHandler {
|
||||
if (this.psdoc?.rid) {
|
||||
this.response.body.rdoc = await record.get(this.args.domainId, this.psdoc.rid);
|
||||
}
|
||||
[this.response.body.ctdocs, this.response.body.htdocs] = await Promise.all([
|
||||
[this.response.body.ctdocs, this.response.body.htdocs] = (await Promise.all([
|
||||
contest.getRelated(this.args.domainId, this.pdoc.docId),
|
||||
contest.getRelated(this.args.domainId, this.pdoc.docId, 'homework'),
|
||||
]);
|
||||
])).map((tdocs) => tdocs.filter((tdoc) => !tdoc.assign?.length || Set.intersection(tdoc.assign, this.user.group).size));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,7 +1070,7 @@ export async function apply(ctx: Context) {
|
||||
ctx.Route('problem_solution_reply_raw', '/p/:pid/solution/:psid/:psrid/raw', ProblemSolutionRawHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
ctx.Route('problem_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM);
|
||||
ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
|
||||
ctx.inject(['api'], ({ api }) => {
|
||||
await ctx.inject(['api'], ({ api }) => {
|
||||
api.provide(ProblemApi);
|
||||
});
|
||||
}
|
||||
@@ -625,7 +625,7 @@ export async function apply(ctx: Context) {
|
||||
throw new NotFoundError();
|
||||
},
|
||||
});
|
||||
ctx.inject(['api'], ({ api }) => {
|
||||
await ctx.inject(['api'], ({ api }) => {
|
||||
api.provide(UserApi);
|
||||
});
|
||||
}
|
||||
@@ -54,7 +54,8 @@ inject('Nav', 'record_main', {
|
||||
query: (handler) => (handler.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
||||
? ({ uidOrName: handler.user._id })
|
||||
: ({})),
|
||||
});
|
||||
}, (handler) => (handler.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
||||
? true : handler.user.hasPerm(PERM.PERM_VIEW_RECORD)));
|
||||
inject('Nav', 'ranking', { prefix: 'ranking' }, PERM.PERM_VIEW_RANKING);
|
||||
inject('Nav', 'domain_dashboard', { prefix: 'domain' }, PERM.PERM_EDIT_DOMAIN);
|
||||
inject('Nav', 'manage_dashboard', { prefix: 'manage' }, PRIV.PRIV_EDIT_SYSTEM);
|
||||
|
||||
@@ -50,7 +50,7 @@ export class Loader extends Service {
|
||||
public cache: Record<string, string> = Object.create(null);
|
||||
// public warnings: Record<string, string> = Object.create(null);
|
||||
|
||||
static inject = ['config', 'timer', 'i18n', 'logger'];
|
||||
static inject = ['setting', 'timer', 'i18n', 'logger'];
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'loader');
|
||||
@@ -100,8 +100,8 @@ export class Loader extends Service {
|
||||
const schemaRequest = configScope ? Schema.object({
|
||||
[configScope]: schema,
|
||||
}) : schema;
|
||||
await this.ctx.config._tryMigrateConfig(schemaRequest);
|
||||
const res = this.ctx.config.requestConfig(schemaRequest);
|
||||
await this.ctx.setting._tryMigrateConfig(schemaRequest);
|
||||
const res = this.ctx.setting.requestConfig(schemaRequest);
|
||||
return configScope ? res[configScope] : res;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
/* eslint-disable max-len */
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import fs from 'fs';
|
||||
import * as cordis from 'cordis';
|
||||
import yaml from 'js-yaml';
|
||||
import { Dictionary } from 'lodash';
|
||||
import moment from 'moment-timezone';
|
||||
import Schema from 'schemastery';
|
||||
import { LangConfig, parseLang } from '@hydrooj/common';
|
||||
import { findFileSync, retry } from '@hydrooj/utils';
|
||||
import { Context, Service } from '../context';
|
||||
import { Context } from '../context';
|
||||
import { Setting as _Setting } from '../interface';
|
||||
import { Logger } from '../logger';
|
||||
import * as builtin from './builtin';
|
||||
@@ -27,11 +26,6 @@ for (const country of countries) {
|
||||
const timezones = Array.from(tzs).sort().map((tz) => [tz, tz]) as [string, string][];
|
||||
const langRange: Dictionary<string> = {};
|
||||
|
||||
for (const lang in global.Hydro.locales) {
|
||||
if (!global.Hydro.locales[lang].__interface) continue;
|
||||
langRange[lang] = global.Hydro.locales[lang].__langname;
|
||||
}
|
||||
|
||||
export const FLAG_HIDDEN = 1;
|
||||
export const FLAG_DISABLED = 2;
|
||||
export const FLAG_SECRET = 4;
|
||||
@@ -89,7 +83,7 @@ function schemaToSettings(schema: Schema<any>) {
|
||||
let flag = (s.meta?.hidden ? FLAG_HIDDEN : 0)
|
||||
| (s.meta?.disabled ? FLAG_DISABLED : 0);
|
||||
const type = s.type === 'number' ? 'number'
|
||||
: s.type === 'boolean' ? 'checkbox'
|
||||
: s.type === 'boolean' ? 'boolean'
|
||||
: s.meta?.role === 'markdown' ? 'markdown'
|
||||
: s.meta?.role === 'textarea' ? 'textarea' : 'text';
|
||||
if (s.meta?.role === 'password') flag |= FLAG_SECRET;
|
||||
@@ -362,39 +356,13 @@ SystemSetting(
|
||||
|
||||
export const langs: Record<string, LangConfig> = {};
|
||||
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
setting: SettingService;
|
||||
}
|
||||
}
|
||||
|
||||
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
||||
function method(this: cordis.Service, ...args: Parameters<F>) {
|
||||
this.ctx.effect(() => {
|
||||
const res = origFunc(...args);
|
||||
return () => (disposeFunc ? disposeFunc(res) : res());
|
||||
});
|
||||
};
|
||||
|
||||
export class SettingService extends Service {
|
||||
PreferenceSetting = T(PreferenceSetting);
|
||||
AccountSetting = T(AccountSetting);
|
||||
DomainSetting = T(DomainSetting);
|
||||
DomainUserSetting = T(DomainUserSetting);
|
||||
SystemSetting = T(SystemSetting);
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'setting');
|
||||
}
|
||||
|
||||
get(key: string) {
|
||||
return (this.ctx ? this.ctx.domain?.config?.[key.replace(/\./g, '$')] : null) ?? global.Hydro.model.system.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
export const inject = ['db'];
|
||||
export async function apply(ctx: Context) {
|
||||
ctx.plugin(SettingService);
|
||||
logger.info('Ensuring settings');
|
||||
for (const lang in global.Hydro.locales) {
|
||||
if (!global.Hydro.locales[lang].__interface) continue;
|
||||
langRange[lang] = global.Hydro.locales[lang].__langname;
|
||||
}
|
||||
const system = global.Hydro.model.system;
|
||||
for (const setting of SYSTEM_SETTINGS) {
|
||||
if (!setting.value) continue;
|
||||
@@ -424,7 +392,6 @@ global.Hydro.model.setting = {
|
||||
apply,
|
||||
inject,
|
||||
|
||||
SettingService,
|
||||
Setting,
|
||||
PreferenceSetting,
|
||||
AccountSetting,
|
||||
|
||||
@@ -159,7 +159,7 @@ export class User {
|
||||
return user;
|
||||
}
|
||||
|
||||
serialize(_, h) {
|
||||
serialize(h) {
|
||||
if (!this._isPrivate) {
|
||||
const fields = ['_id', 'uname', 'mail', 'perm', 'role', 'priv', 'regat', 'loginat', 'tfa', 'authn'];
|
||||
if (h?.user?.hasPerm(PERM.PERM_VIEW_DISPLAYNAME)) fields.push('displayName');
|
||||
@@ -189,6 +189,7 @@ async function initAndCache(udoc: Udoc, dudoc, scope: bigint = PERM.PERM_ALL) {
|
||||
|
||||
class UserModel {
|
||||
static coll = coll;
|
||||
static collGroup = collGroup;
|
||||
static User = User;
|
||||
static cache = cache;
|
||||
static defaultUser: Udoc = {
|
||||
|
||||
@@ -2,6 +2,7 @@ import pwsh from './lib/hash.hydro';
|
||||
import db from './service/db';
|
||||
|
||||
export { nanoid } from 'nanoid';
|
||||
export { default as moment, isMoment } from 'moment-timezone';
|
||||
|
||||
export {
|
||||
WebService, Router, HandlerCommon, httpServer,
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/* eslint-disable no-await-in-loop */
|
||||
import { resolve } from 'path';
|
||||
import {
|
||||
existsSync, mkdir, readdir, rename, rmdir,
|
||||
} from 'fs-extra';
|
||||
import Schema from 'schemastery';
|
||||
import { Context } from '../context';
|
||||
|
||||
export const apply = (ctx: Context) => ctx.addScript(
|
||||
'fixStorage', 'Rename all files to lowercase',
|
||||
Schema.object({}),
|
||||
async () => {
|
||||
const config = await ctx.get('storage').status();
|
||||
console.log(config);
|
||||
if (config.type !== 'Local') throw new Error('Only local storage is supported');
|
||||
const dir = (config as any).dir;
|
||||
if (!dir) throw new Error('Storage dir not found');
|
||||
const files = await readdir(dir);
|
||||
for (const file of files) {
|
||||
let inner = await readdir(resolve(dir, file));
|
||||
if (file.toLowerCase() !== file && !existsSync(resolve(dir, file.toLowerCase()))) {
|
||||
await mkdir(resolve(dir, file.toLowerCase()));
|
||||
console.log('+: ', resolve(dir, file.toLowerCase()));
|
||||
}
|
||||
for (const f of inner) {
|
||||
if (resolve(dir, file, f) === resolve(dir, file.toLowerCase(), f.toLowerCase())) continue;
|
||||
await rename(resolve(dir, file, f), resolve(dir, file.toLowerCase(), f.toLowerCase()));
|
||||
console.log('R: ', resolve(dir, file, f), '->', resolve(dir, file.toLowerCase(), f.toLowerCase()));
|
||||
}
|
||||
inner = await readdir(resolve(dir, file));
|
||||
if (!inner.length) {
|
||||
await rmdir(resolve(dir, file));
|
||||
console.log('-: ', resolve(dir, file));
|
||||
}
|
||||
}
|
||||
const coll = ctx.get('db').collection('storage');
|
||||
while (true) {
|
||||
let hasItem = false;
|
||||
for await (const t of coll.find({ $or: [{ _id: /[A-Z]/ }, { link: /[A-Z]/ }] })) {
|
||||
hasItem = true;
|
||||
await coll.insertOne({
|
||||
...t,
|
||||
_id: t._id.toLowerCase(),
|
||||
...(t.link ? { link: t.link.toLowerCase() } : {}),
|
||||
});
|
||||
await coll.deleteOne({ _id: t._id });
|
||||
console.log('Q: ', t._id, '->', t._id.toLowerCase());
|
||||
}
|
||||
if (!hasItem) break;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
@@ -91,7 +91,7 @@ export async function apply(ctx: Context) {
|
||||
xhost: system.get('server.xhost'),
|
||||
});
|
||||
if (process.env.HYDRO_CLI) return;
|
||||
ctx.inject(['server', 'oauth', 'setting'], (childContext) => {
|
||||
await ctx.inject(['server', 'oauth', 'setting'], (childContext) => {
|
||||
const { server, on, oauth } = childContext;
|
||||
server.addHandlerLayer('init', async (c, next) => {
|
||||
const init = Date.now();
|
||||
|
||||
@@ -387,6 +387,7 @@ class LocalStorageService {
|
||||
status: !this.error,
|
||||
error: this.error,
|
||||
bucket: 'Hydro',
|
||||
dir: this.config.path,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -400,7 +401,7 @@ export async function apply(ctx: Context, config: ReturnType<typeof FileSetting>
|
||||
service = new LocalStorageService(config);
|
||||
}
|
||||
await service.start();
|
||||
ctx.inject(['server'], ({ server }) => {
|
||||
await ctx.inject(['server'], ({ server }) => {
|
||||
let endpoint = config.endPoint;
|
||||
if (config.type === 's3' && !config.pathStyle) {
|
||||
try {
|
||||
|
||||
@@ -2,18 +2,29 @@ import yaml from 'js-yaml';
|
||||
import Schema from 'schemastery';
|
||||
import { Context, Service } from './context';
|
||||
import { Logger } from './logger';
|
||||
import {
|
||||
AccountSetting, DomainSetting, DomainUserSetting, PreferenceSetting, SystemSetting,
|
||||
} from './model/setting';
|
||||
|
||||
const logger = new Logger('settings');
|
||||
|
||||
declare module './context' {
|
||||
declare module 'cordis' {
|
||||
interface Context {
|
||||
config: ConfigService;
|
||||
setting: SettingService;
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigService extends Service {
|
||||
const T = <F extends (...args: any[]) => any>(origFunc: F, disposeFunc?) =>
|
||||
function method(this: Service, ...args: Parameters<F>) {
|
||||
this.ctx.effect(() => {
|
||||
const res = origFunc(...args);
|
||||
return () => (disposeFunc ? disposeFunc(res) : res());
|
||||
});
|
||||
};
|
||||
|
||||
export class SettingService extends Service {
|
||||
static inject = ['db'];
|
||||
static name = 'config';
|
||||
static name = 'setting';
|
||||
static blacklist = ['__proto__', 'prototype', 'constructor'];
|
||||
|
||||
settings: Schema[] = [];
|
||||
@@ -23,8 +34,14 @@ export class ConfigService extends Service {
|
||||
private initialValues = {};
|
||||
private _lastMigrate = Promise.resolve();
|
||||
|
||||
PreferenceSetting = T(PreferenceSetting);
|
||||
AccountSetting = T(AccountSetting);
|
||||
DomainSetting = T(DomainSetting);
|
||||
DomainUserSetting = T(DomainUserSetting);
|
||||
SystemSetting = T(SystemSetting);
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'config');
|
||||
super(ctx, 'setting');
|
||||
}
|
||||
|
||||
async [Context.init]() {
|
||||
@@ -54,7 +71,7 @@ export class ConfigService extends Service {
|
||||
|
||||
applyDelta(source: any, key: string, value: any) {
|
||||
const path = key.split('.');
|
||||
if (path.some((i) => ConfigService.blacklist.includes(i))) return false;
|
||||
if (path.some((i) => SettingService.blacklist.includes(i))) return false;
|
||||
const t = path.pop();
|
||||
const root = JSON.parse(JSON.stringify(source));
|
||||
let cursor = root;
|
||||
@@ -111,9 +128,9 @@ export class ConfigService extends Service {
|
||||
await this._lastMigrate;
|
||||
}
|
||||
|
||||
get(key: string) {
|
||||
_get(key: string) {
|
||||
const parts = key.split('.');
|
||||
if (parts.some((p) => ConfigService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
||||
if (parts.some((p) => SettingService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
||||
let currentValue = this.applied;
|
||||
for (const p of parts) {
|
||||
if (!currentValue) return undefined;
|
||||
@@ -122,6 +139,11 @@ export class ConfigService extends Service {
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
get(key: string) {
|
||||
return (this.ctx ? this.ctx.domain?.config?.[key.replace(/\./g, '$')] : null)
|
||||
?? (this._get(key) || global.Hydro?.model?.system?.get?.(key));
|
||||
}
|
||||
|
||||
async setConfig(key: string, value: any) {
|
||||
const newConfig = this.applyDelta(this.systemConfig, key, value);
|
||||
await this.saveConfig(newConfig);
|
||||
@@ -150,7 +172,7 @@ export class ConfigService extends Service {
|
||||
});
|
||||
const that = this;
|
||||
const getAccess = (path: (string | symbol)[]) => {
|
||||
if (path.some((p) => ConfigService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
||||
if (path.some((p) => SettingService.blacklist.includes(p.toString()))) throw new Error('Invalid path');
|
||||
let currentValue = curValue;
|
||||
for (const p of path) {
|
||||
currentValue = currentValue[p];
|
||||
|
||||
@@ -290,7 +290,7 @@ export const coreScripts: MigrationScript[] = [
|
||||
'file.pathStyle', 'file.endPointForUser', 'file.endPointForJudge',
|
||||
] as any[]) as any;
|
||||
if ((endPoint && accessKey) || process.env.MINIO_ACCESS_KEY) {
|
||||
await app.get('config').setConfig('file', {
|
||||
await app.get('setting').setConfig('file', {
|
||||
type: 's3',
|
||||
endPoint: process.env.MINIO_ACCESS_KEY ? 'http://127.0.0.1:9000/' : endPoint,
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || accessKey,
|
||||
|
||||
Reference in new issue
Block a user