chore(admin): remove useless config diff (#12545)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Added a GraphQL mutation to validate multiple app configuration updates, returning detailed validation results for each item.
  - Extended the API schema to support validation feedback, enabling client-side checks before applying changes.
  - Introduced a detailed, parameterized error message system for configuration validation errors.
  - Enabled validation of configuration inputs via the admin UI with clear, descriptive error messages.

- **Improvements**
  - Enhanced error reporting with specific, context-rich messages for invalid app configurations.
  - Simplified admin settings UI by removing the confirmation dialog and streamlining save actions.
  - Improved clarity and maintainability of validation logic and error handling components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
forehalo
2025-05-27 06:07:26 +00:00
parent eed95366c9
commit 2f139bd02c
19 changed files with 291 additions and 185 deletions

View File

@@ -4,6 +4,7 @@ import Sinon from 'sinon';
import { createModule } from '../../../__tests__/create-module';
import { Mockers } from '../../../__tests__/mocks';
import { InvalidAppConfigInput } from '../../../base';
import { Models } from '../../../models';
import { ServerService } from '../service';
@@ -47,9 +48,7 @@ test('should validate config before update', async t => {
},
]),
{
message: `Invalid config for module [server] with key [externalUrl]
Value: "invalid-url@some-domain.com"
Error: Invalid url`,
instanceOf: InvalidAppConfigInput,
}
);
@@ -64,7 +63,7 @@ Error: Invalid url`,
},
]),
{
message: `Invalid config for module [auth] with unknown key [unknown-key]`,
instanceOf: InvalidAppConfigInput,
}
);

View File

@@ -182,6 +182,24 @@ class UpdateAppConfigInput {
value!: any;
}
@ObjectType()
class AppConfigValidateResult {
@Field()
module!: string;
@Field()
key!: string;
@Field(() => GraphQLJSON)
value!: any;
@Field()
valid!: boolean;
@Field(() => String, { nullable: true })
error?: string;
}
@Admin()
@Resolver(() => GraphQLJSONObject)
export class AppConfigResolver {
@@ -204,4 +222,28 @@ export class AppConfigResolver {
): Promise<DeepPartial<AppConfig>> {
return await this.service.updateConfig(me.id, updates);
}
@Mutation(() => [AppConfigValidateResult], {
description: 'validate app configuration',
})
async validateAppConfig(
@Args('updates', { type: () => [UpdateAppConfigInput] })
updates: UpdateAppConfigInput[]
): Promise<AppConfigValidateResult[]> {
const errors = this.service.validateConfig(updates);
return updates.map(update => {
const error = errors?.find(
error =>
error.data.module === update.module && error.data.key === update.key
);
return {
module: update.module,
key: update.key,
value: update.value,
valid: !error,
error: error?.data.hint,
};
});
}
}

View File

@@ -1,7 +1,12 @@
import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common';
import { set } from 'lodash-es';
import { ConfigFactory, EventBus, OnEvent } from '../../base';
import {
ConfigFactory,
EventBus,
InvalidAppConfigInput,
OnEvent,
} from '../../base';
import { Models } from '../../models';
import { ServerFeature } from './types';
@@ -60,11 +65,21 @@ export class ServerService implements OnApplicationBootstrap {
return this.configFactory.clone();
}
validateConfig(updates: Array<{ module: string; key: string; value: any }>) {
return this.configFactory.validate(updates);
}
async updateConfig(
user: string,
updates: Array<{ module: string; key: string; value: any }>
): Promise<DeepPartial<AppConfig>> {
this.configFactory.validate(updates);
const errors = this.configFactory.validate(updates);
if (errors?.length) {
throw new InvalidAppConfigInput({
message: errors.map(error => error.message).join('\n'),
});
}
const promises = await this.models.appConfig.save(
user,