core: rename ConfigService to SettingService

This commit is contained in:
undefined committed 2025-06-08 21:42:03 +08:00
1 parent 82735ea3cb
commit c7117ef3ea
15 files changed
+59 -69

No files matched your search

+4 -4
View File
@@ -6,7 +6,7 @@ import { ObjectId } from 'mongodb';
import { Context } from '../context'; import { Context } from '../context';
import { load as loadOptions } from '../options'; import { load as loadOptions } from '../options';
import { MongoService } from '../service/db'; import { MongoService } from '../service/db';
import { ConfigService } from '../settings'; import { SettingService } from '../settings';
import { import {
addon, builtinModel, model, service, addon, builtinModel, model, service,
} from './common'; } from './common';
@@ -110,10 +110,10 @@ export async function load(ctx: Context) {
const pending = global.addons; const pending = global.addons;
const fail = []; const fail = [];
await ctx.plugin(MongoService, loadOptions() || {}); await ctx.plugin(MongoService, loadOptions() || {});
await ctx.plugin(ConfigService); await ctx.plugin(SettingService);
await require('../model/system').runConfig(); await require('../model/system').runConfig();
ctx = await new Promise((resolve) => { ctx = await new Promise((resolve) => {
ctx.inject(['loader', 'config', 'db'], (c) => { ctx.inject(['loader', 'setting', 'db'], (c) => {
resolve(c); resolve(c);
}); });
}); });
@@ -127,7 +127,7 @@ export async function load(ctx: Context) {
await builtinModel(ctx); await builtinModel(ctx);
await model(pending, fail, ctx); await model(pending, fail, ctx);
ctx = await new Promise((resolve) => { ctx = await new Promise((resolve) => {
ctx.inject(['server', 'setting', 'worker'], (c) => { ctx.inject(['server', 'worker'], (c) => {
resolve(c); resolve(c);
}); });
}); });
+3 -3
View File
@@ -7,7 +7,7 @@ import { Context } from '../context';
import { Logger } from '../logger'; import { Logger } from '../logger';
import { load } from '../options'; import { load } from '../options';
import { MongoService } from '../service/db'; import { MongoService } from '../service/db';
import { ConfigService } from '../settings'; import { SettingService } from '../settings';
import { import {
addon, builtinModel, locale, model, service, addon, builtinModel, locale, model, service,
} from './common'; } from './common';
@@ -30,11 +30,11 @@ export async function apply(ctx: Context) {
const fail = []; const fail = [];
await locale(pending, fail); await locale(pending, fail);
await ctx.plugin(MongoService, load() || {}); await ctx.plugin(MongoService, load() || {});
await ctx.plugin(ConfigService); await ctx.plugin(SettingService);
const modelSystem = require('../model/system'); const modelSystem = require('../model/system');
await modelSystem.runConfig(); await modelSystem.runConfig();
ctx = await new Promise((resolve) => { ctx = await new Promise((resolve) => {
ctx.inject(['loader', 'config', 'db'], (c) => { ctx.inject(['loader', 'setting', 'db'], (c) => {
resolve(c); resolve(c);
}); });
}); });
+1 -1
View File
@@ -743,7 +743,7 @@ export async function apply(ctx: Context) {
await Promise.all(tasks); await Promise.all(tasks);
}); });
ctx.plugin(ScoreboardService); 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', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST_SCOREBOARD);
Route('contest_scoreboard_view', '/contest/:tid/scoreboard/:view', 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 }, { scoreboard.addView('default', 'Default', { tdoc: 'tdoc', groups: 'groups', realtime: Types.Boolean }, {
+1 -1
View File
@@ -461,7 +461,7 @@ export async function apply(ctx: Context) {
ctx.Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler); ctx.Route('domain_join_applications', '/domain/join_applications', DomainJoinApplicationsHandler);
ctx.Route('domain_join', '/domain/join', DomainJoinHandler, PRIV.PRIV_USER_PROFILE); ctx.Route('domain_join', '/domain/join', DomainJoinHandler, PRIV.PRIV_USER_PROFILE);
ctx.Route('domain_search', '/domain/search', DomainSearchHandler, 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); api.provide(DomainApi);
}); });
} }
+1 -1
View File
@@ -314,7 +314,7 @@ export async function apply(ctx) {
ctx.Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler); ctx.Route('homework_edit', '/homework/:tid/edit', HomeworkEditHandler);
ctx.Route('homework_files', '/homework/:tid/file', HomeworkFilesHandler, PERM.PERM_VIEW_HOMEWORK); 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.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', '/homework/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
Route('homework_scoreboard_view', '/homework/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD); Route('homework_scoreboard_view', '/homework/:tid/scoreboard/:view', ContestScoreboardHandler, PERM.PERM_VIEW_HOMEWORK_SCOREBOARD);
}); });
+8 -8
View File
@@ -199,14 +199,14 @@ class SystemConfigHandler extends SystemHandler {
try { try {
try { try {
value = Schema.intersect(this.ctx.config.settings)(yaml.load(this.ctx.config.configSource)); value = Schema.intersect(this.ctx.setting.settings)(yaml.load(this.ctx.setting.configSource));
} catch (e) { } catch (e) {
value = yaml.load(this.ctx.config.configSource); value = yaml.load(this.ctx.setting.configSource);
} }
for (const schema of this.ctx.config.settings) processNode(value, schema); for (const schema of this.ctx.setting.settings) processNode(value, schema);
} catch (e) { } } catch (e) { }
this.response.body = { this.response.body = {
schema: Schema.intersect(this.ctx.config.settings).toJSON(), schema: Schema.intersect(this.ctx.setting.settings).toJSON(),
value: yaml.dump(value), value: yaml.dump(value),
}; };
} }
@@ -214,7 +214,7 @@ class SystemConfigHandler extends SystemHandler {
@requireSudo @requireSudo
@param('value', Types.String) @param('value', Types.String)
async post({ }, value: string) { async post({ }, value: string) {
const oldConfig = yaml.load(this.ctx.config.configSource); const oldConfig = yaml.load(this.ctx.setting.configSource);
let config; let config;
const processNode = (node: any, old: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => { const processNode = (node: any, old: any, schema: Schema<any, any>, parent?: any, accessKey?: string) => {
if (['union', 'intersect'].includes(schema.type)) { if (['union', 'intersect'].includes(schema.type)) {
@@ -231,11 +231,11 @@ class SystemConfigHandler extends SystemHandler {
try { try {
config = yaml.load(value); 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) { } catch (e) {
throw new ValidationError('value', '', e.message); throw new ValidationError('value', '', e.message);
} }
await this.ctx.config.saveConfig(config); await this.ctx.setting.saveConfig(config);
} }
} }
@@ -357,7 +357,7 @@ class SystemUserPrivHandler extends SystemHandler {
} }
} }
export const inject = ['config', 'check']; export const inject = ['setting', 'check'];
export async function apply(ctx) { export async function apply(ctx) {
ctx.Route('manage', '/manage', SystemMainHandler); ctx.Route('manage', '/manage', SystemMainHandler);
ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler); ctx.Route('manage_dashboard', '/manage/dashboard', SystemDashboardHandler);
+1 -1
View File
@@ -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_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_statistics', '/p/:pid/stat', ProblemStatisticsHandler, PERM.PERM_VIEW_PROBLEM);
ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM); ctx.Route('problem_create', '/problem/create', ProblemCreateHandler, PERM.PERM_CREATE_PROBLEM);
ctx.inject(['api'], ({ api }) => { await ctx.inject(['api'], ({ api }) => {
api.provide(ProblemApi); api.provide(ProblemApi);
}); });
} }
+1 -1
View File
@@ -625,7 +625,7 @@ export async function apply(ctx: Context) {
throw new NotFoundError(); throw new NotFoundError();
}, },
}); });
ctx.inject(['api'], ({ api }) => { await ctx.inject(['api'], ({ api }) => {
api.provide(UserApi); api.provide(UserApi);
}); });
} }
+3 -3
View File
@@ -50,7 +50,7 @@ export class Loader extends Service {
public cache: Record<string, string> = Object.create(null); public cache: Record<string, string> = Object.create(null);
// public warnings: 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) { constructor(ctx: Context) {
super(ctx, 'loader'); super(ctx, 'loader');
@@ -100,8 +100,8 @@ export class Loader extends Service {
const schemaRequest = configScope ? Schema.object({ const schemaRequest = configScope ? Schema.object({
[configScope]: schema, [configScope]: schema,
}) : schema; }) : schema;
await this.ctx.config._tryMigrateConfig(schemaRequest); await this.ctx.setting._tryMigrateConfig(schemaRequest);
const res = this.ctx.config.requestConfig(schemaRequest); const res = this.ctx.setting.requestConfig(schemaRequest);
return configScope ? res[configScope] : res; return configScope ? res[configScope] : res;
} }
+1 -33
View File
@@ -1,14 +1,13 @@
/* eslint-disable max-len */ /* eslint-disable max-len */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
import fs from 'fs'; import fs from 'fs';
import * as cordis from 'cordis';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { Dictionary } from 'lodash'; import { Dictionary } from 'lodash';
import moment from 'moment-timezone'; import moment from 'moment-timezone';
import Schema from 'schemastery'; import Schema from 'schemastery';
import { LangConfig, parseLang } from '@hydrooj/common'; import { LangConfig, parseLang } from '@hydrooj/common';
import { findFileSync, retry } from '@hydrooj/utils'; import { findFileSync, retry } from '@hydrooj/utils';
import { Context, Service } from '../context'; import { Context } from '../context';
import { Setting as _Setting } from '../interface'; import { Setting as _Setting } from '../interface';
import { Logger } from '../logger'; import { Logger } from '../logger';
import * as builtin from './builtin'; import * as builtin from './builtin';
@@ -362,38 +361,8 @@ SystemSetting(
export const langs: Record<string, LangConfig> = {}; 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 const inject = ['db'];
export async function apply(ctx: Context) { export async function apply(ctx: Context) {
ctx.plugin(SettingService);
logger.info('Ensuring settings'); logger.info('Ensuring settings');
const system = global.Hydro.model.system; const system = global.Hydro.model.system;
for (const setting of SYSTEM_SETTINGS) { for (const setting of SYSTEM_SETTINGS) {
@@ -424,7 +393,6 @@ global.Hydro.model.setting = {
apply, apply,
inject, inject,
SettingService,
Setting, Setting,
PreferenceSetting, PreferenceSetting,
AccountSetting, AccountSetting,
+1 -1
View File
@@ -91,7 +91,7 @@ export async function apply(ctx: Context) {
xhost: system.get('server.xhost'), xhost: system.get('server.xhost'),
}); });
if (process.env.HYDRO_CLI) return; if (process.env.HYDRO_CLI) return;
ctx.inject(['server', 'oauth', 'setting'], (childContext) => { await ctx.inject(['server', 'oauth', 'setting'], (childContext) => {
const { server, on, oauth } = childContext; const { server, on, oauth } = childContext;
server.addHandlerLayer('init', async (c, next) => { server.addHandlerLayer('init', async (c, next) => {
const init = Date.now(); const init = Date.now();
+1 -1
View File
@@ -400,7 +400,7 @@ export async function apply(ctx: Context, config: ReturnType<typeof FileSetting>
service = new LocalStorageService(config); service = new LocalStorageService(config);
} }
await service.start(); await service.start();
ctx.inject(['server'], ({ server }) => { await ctx.inject(['server'], ({ server }) => {
let endpoint = config.endPoint; let endpoint = config.endPoint;
if (config.type === 's3' && !config.pathStyle) { if (config.type === 's3' && !config.pathStyle) {
try { try {
+31 -9
View File
@@ -2,18 +2,29 @@ import yaml from 'js-yaml';
import Schema from 'schemastery'; import Schema from 'schemastery';
import { Context, Service } from './context'; import { Context, Service } from './context';
import { Logger } from './logger'; import { Logger } from './logger';
import {
AccountSetting, DomainSetting, DomainUserSetting, PreferenceSetting, SystemSetting,
} from './model/setting';
const logger = new Logger('settings'); const logger = new Logger('settings');
declare module './context' { declare module 'cordis' {
interface Context { 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 inject = ['db'];
static name = 'config'; static name = 'setting';
static blacklist = ['__proto__', 'prototype', 'constructor']; static blacklist = ['__proto__', 'prototype', 'constructor'];
settings: Schema[] = []; settings: Schema[] = [];
@@ -23,8 +34,14 @@ export class ConfigService extends Service {
private initialValues = {}; private initialValues = {};
private _lastMigrate = Promise.resolve(); private _lastMigrate = Promise.resolve();
PreferenceSetting = T(PreferenceSetting);
AccountSetting = T(AccountSetting);
DomainSetting = T(DomainSetting);
DomainUserSetting = T(DomainUserSetting);
SystemSetting = T(SystemSetting);
constructor(ctx: Context) { constructor(ctx: Context) {
super(ctx, 'config'); super(ctx, 'setting');
} }
async [Context.init]() { async [Context.init]() {
@@ -54,7 +71,7 @@ export class ConfigService extends Service {
applyDelta(source: any, key: string, value: any) { applyDelta(source: any, key: string, value: any) {
const path = key.split('.'); 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 t = path.pop();
const root = JSON.parse(JSON.stringify(source)); const root = JSON.parse(JSON.stringify(source));
let cursor = root; let cursor = root;
@@ -111,9 +128,9 @@ export class ConfigService extends Service {
await this._lastMigrate; await this._lastMigrate;
} }
get(key: string) { _get(key: string) {
const parts = key.split('.'); 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; let currentValue = this.applied;
for (const p of parts) { for (const p of parts) {
if (!currentValue) return undefined; if (!currentValue) return undefined;
@@ -122,6 +139,11 @@ export class ConfigService extends Service {
return currentValue; 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) { async setConfig(key: string, value: any) {
const newConfig = this.applyDelta(this.systemConfig, key, value); const newConfig = this.applyDelta(this.systemConfig, key, value);
await this.saveConfig(newConfig); await this.saveConfig(newConfig);
@@ -150,7 +172,7 @@ export class ConfigService extends Service {
}); });
const that = this; const that = this;
const getAccess = (path: (string | symbol)[]) => { 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; let currentValue = curValue;
for (const p of path) { for (const p of path) {
currentValue = currentValue[p]; currentValue = currentValue[p];
+1 -1
View File
@@ -290,7 +290,7 @@ export const coreScripts: MigrationScript[] = [
'file.pathStyle', 'file.endPointForUser', 'file.endPointForJudge', 'file.pathStyle', 'file.endPointForUser', 'file.endPointForJudge',
] as any[]) as any; ] as any[]) as any;
if ((endPoint && accessKey) || process.env.MINIO_ACCESS_KEY) { if ((endPoint && accessKey) || process.env.MINIO_ACCESS_KEY) {
await app.get('config').setConfig('file', { await app.get('setting').setConfig('file', {
type: 's3', type: 's3',
endPoint: process.env.MINIO_ACCESS_KEY ? 'http://127.0.0.1:9000/' : endPoint, endPoint: process.env.MINIO_ACCESS_KEY ? 'http://127.0.0.1:9000/' : endPoint,
accessKey: process.env.MINIO_ACCESS_KEY || accessKey, accessKey: process.env.MINIO_ACCESS_KEY || accessKey,
+1 -1
View File
@@ -76,7 +76,7 @@ class MarkdownHandler extends Handler {
class SystemConfigSchemaHandler extends Handler { class SystemConfigSchemaHandler extends Handler {
async get() { async get() {
const schema = convert(Schema.intersect(this.ctx.config.settings) as any, true); const schema = convert(Schema.intersect(this.ctx.setting.settings) as any, true);
this.response.body = schema; this.response.body = schema;
} }
} }