All checks were successful
Test Asgard Runner / test (push) Successful in 3s
- servers.service: sendCommand() now throws InternalServerErrorException on
NATS failure instead of silently succeeding; returns { success, message }
instead of the legacy { output } shape; adds NestJS Logger
- plugins.service: installPlugin() dispatches plugin_install to
corrosion.{license_id}.cmd.server after DB save; NATS failure is logged
but non-fatal so the DB record is preserved regardless
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
132 lines
4.1 KiB
TypeScript
132 lines
4.1 KiB
TypeScript
import { Injectable, NotFoundException, ConflictException, Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { PluginRegistry } from '../../entities/plugin-registry.entity';
|
|
import { InstallPluginDto } from './dto/install-plugin.dto';
|
|
import { UpdatePluginConfigDto } from './dto/update-plugin-config.dto';
|
|
import { NatsService } from '../../services/nats.service';
|
|
|
|
@Injectable()
|
|
export class PluginsService {
|
|
private readonly logger = new Logger(PluginsService.name);
|
|
|
|
constructor(
|
|
@InjectRepository(PluginRegistry)
|
|
private readonly pluginRegistryRepo: Repository<PluginRegistry>,
|
|
private readonly natsService: NatsService,
|
|
) {}
|
|
|
|
async getPlugins(licenseId: string): Promise<PluginRegistry[]> {
|
|
return this.pluginRegistryRepo.find({
|
|
where: { license_id: licenseId },
|
|
order: { installed_at: 'DESC' },
|
|
});
|
|
}
|
|
|
|
async installPlugin(licenseId: string, dto: InstallPluginDto): Promise<PluginRegistry> {
|
|
// Check if plugin already exists
|
|
const existing = await this.pluginRegistryRepo.findOne({
|
|
where: {
|
|
license_id: licenseId,
|
|
plugin_name: dto.plugin_name,
|
|
},
|
|
});
|
|
|
|
if (existing) {
|
|
throw new ConflictException(`Plugin ${dto.plugin_name} is already installed`);
|
|
}
|
|
|
|
const plugin = this.pluginRegistryRepo.create({
|
|
license_id: licenseId,
|
|
plugin_name: dto.plugin_name,
|
|
umod_slug: dto.umod_slug,
|
|
source: dto.source || 'manual',
|
|
is_installed: true,
|
|
is_loaded: false,
|
|
});
|
|
|
|
const saved = await this.pluginRegistryRepo.save(plugin);
|
|
|
|
try {
|
|
await this.natsService.publish(`corrosion.${licenseId}.cmd.server`, {
|
|
action: 'plugin_install',
|
|
plugin_name: dto.plugin_name,
|
|
umod_slug: dto.umod_slug,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
this.logger.log(`Plugin install dispatched for ${dto.plugin_name} on license ${licenseId}`);
|
|
} catch (err) {
|
|
this.logger.error(`Failed to dispatch plugin install for ${dto.plugin_name} on license ${licenseId}: ${(err as Error).message}`);
|
|
}
|
|
|
|
return saved;
|
|
}
|
|
|
|
async uninstallPlugin(licenseId: string, pluginId: string): Promise<void> {
|
|
const plugin = await this.pluginRegistryRepo.findOne({
|
|
where: { id: pluginId, license_id: licenseId },
|
|
});
|
|
|
|
if (!plugin) {
|
|
throw new NotFoundException(`Plugin ${pluginId} not found`);
|
|
}
|
|
|
|
await this.pluginRegistryRepo.delete({ id: pluginId, license_id: licenseId });
|
|
|
|
await this.natsService.publish(`corrosion.${licenseId}.cmd.plugin`, {
|
|
action: 'unload',
|
|
plugin_name: plugin.plugin_name,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
this.logger.log(`Plugin uninstall dispatched for ${plugin.plugin_name} on license ${licenseId}`);
|
|
}
|
|
|
|
async reloadPlugin(
|
|
licenseId: string,
|
|
pluginId: string,
|
|
): Promise<{ reloaded: boolean; plugin_name: string }> {
|
|
const plugin = await this.pluginRegistryRepo.findOne({
|
|
where: { id: pluginId, license_id: licenseId },
|
|
});
|
|
|
|
if (!plugin) {
|
|
throw new NotFoundException(`Plugin ${pluginId} not found`);
|
|
}
|
|
|
|
await this.natsService.publish(`corrosion.${licenseId}.cmd.plugin`, {
|
|
action: 'reload',
|
|
plugin_name: plugin.plugin_name,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
this.logger.log(`Plugin reload dispatched for ${plugin.plugin_name} on license ${licenseId}`);
|
|
|
|
return { reloaded: true, plugin_name: plugin.plugin_name };
|
|
}
|
|
|
|
async updateConfig(
|
|
licenseId: string,
|
|
pluginId: string,
|
|
dto: UpdatePluginConfigDto,
|
|
): Promise<PluginRegistry> {
|
|
const plugin = await this.pluginRegistryRepo.findOne({
|
|
where: { id: pluginId, license_id: licenseId },
|
|
});
|
|
|
|
if (!plugin) {
|
|
throw new NotFoundException(`Plugin ${pluginId} not found`);
|
|
}
|
|
|
|
Object.assign(plugin, dto);
|
|
plugin.updated_at = new Date();
|
|
return this.pluginRegistryRepo.save(plugin);
|
|
}
|
|
|
|
async searchUmod(query: string): Promise<{ results: any[]; message: string }> {
|
|
// uMod API integration pending
|
|
return {
|
|
results: [],
|
|
message: 'uMod search integration not yet configured',
|
|
};
|
|
}
|
|
}
|