All checks were successful
Test Asgard Runner / test (push) Successful in 2s
DB migrations 016 (kits_configs) and 019 (furnacesplitter_configs) applied. Backend: NestJS modules with CRUD, apply-to-server, import-from-server. Frontend: Pinia stores, Vue views with config editor, router + nav wiring. Kits view: 3-tab editor (list/editor/settings), kit items with shortname/amount/skinId/container. FurnaceSplitter view: per-furnace toggles, split count, fuel multiplier settings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
6.4 KiB
TypeScript
181 lines
6.4 KiB
TypeScript
import { Injectable, Logger, NotFoundException, HttpException, HttpStatus } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { FurnaceSplitterConfig } from '../../entities/furnacesplitter-config.entity';
|
|
import { NatsService } from '../../services/nats.service';
|
|
import { CreateFurnaceSplitterConfigDto } from './dto/create-furnacesplitter-config.dto';
|
|
import { UpdateFurnaceSplitterConfigDto } from './dto/update-furnacesplitter-config.dto';
|
|
|
|
@Injectable()
|
|
export class FurnaceSplitterService {
|
|
private readonly logger = new Logger(FurnaceSplitterService.name);
|
|
|
|
constructor(
|
|
@InjectRepository(FurnaceSplitterConfig)
|
|
private readonly furnaceRepo: Repository<FurnaceSplitterConfig>,
|
|
private readonly natsService: NatsService,
|
|
) {}
|
|
|
|
/** List configs for a license (summaries — no JSONB) */
|
|
async getConfigs(licenseId: string) {
|
|
const configs = await this.furnaceRepo.find({
|
|
where: { license_id: licenseId },
|
|
select: ['id', 'config_name', 'description', 'is_active', 'created_at', 'updated_at'],
|
|
order: { created_at: 'DESC' },
|
|
});
|
|
return { configs };
|
|
}
|
|
|
|
/** Get full config with JSONB data */
|
|
async getConfig(licenseId: string, configId: string) {
|
|
const config = await this.furnaceRepo.findOne({
|
|
where: { id: configId, license_id: licenseId },
|
|
});
|
|
if (!config) throw new NotFoundException('FurnaceSplitter config not found');
|
|
return { config };
|
|
}
|
|
|
|
/** Create a new config */
|
|
async createConfig(licenseId: string, dto: CreateFurnaceSplitterConfigDto) {
|
|
const config = this.furnaceRepo.create({
|
|
license_id: licenseId,
|
|
config_name: dto.config_name,
|
|
description: dto.description || null,
|
|
config_data: dto.config_data || {},
|
|
});
|
|
const saved = await this.furnaceRepo.save(config);
|
|
return { config: saved };
|
|
}
|
|
|
|
/** Update an existing config */
|
|
async updateConfig(licenseId: string, configId: string, dto: UpdateFurnaceSplitterConfigDto) {
|
|
const config = await this.furnaceRepo.findOne({
|
|
where: { id: configId, license_id: licenseId },
|
|
});
|
|
if (!config) throw new NotFoundException('FurnaceSplitter config not found');
|
|
|
|
if (dto.config_name !== undefined) config.config_name = dto.config_name;
|
|
if (dto.description !== undefined) config.description = dto.description;
|
|
if (dto.config_data !== undefined) config.config_data = dto.config_data;
|
|
if (dto.is_active !== undefined) config.is_active = dto.is_active;
|
|
config.updated_at = new Date();
|
|
|
|
const saved = await this.furnaceRepo.save(config);
|
|
return { config: saved };
|
|
}
|
|
|
|
/** Delete a config */
|
|
async deleteConfig(licenseId: string, configId: string) {
|
|
const result = await this.furnaceRepo.delete({ id: configId, license_id: licenseId });
|
|
if (result.affected === 0) throw new NotFoundException('FurnaceSplitter config not found');
|
|
return { deleted: true };
|
|
}
|
|
|
|
/** Deploy config to game server via NATS */
|
|
async applyToServer(licenseId: string, configId: string) {
|
|
const config = await this.furnaceRepo.findOne({
|
|
where: { id: configId, license_id: licenseId },
|
|
});
|
|
if (!config) throw new NotFoundException('FurnaceSplitter config not found');
|
|
|
|
const jsonString = JSON.stringify(config.config_data, null, 2);
|
|
|
|
try {
|
|
// Write FurnaceSplitter.json via file manager NATS
|
|
await this.natsService.request(
|
|
`corrosion.${licenseId}.files.cmd`,
|
|
{
|
|
func: 'fm_save',
|
|
path: 'server://oxide/config/FurnaceSplitter.json',
|
|
content: jsonString,
|
|
},
|
|
30000,
|
|
);
|
|
|
|
// Reload FurnaceSplitter plugin via RCON
|
|
await this.natsService.publish(
|
|
`corrosion.${licenseId}.cmd.server`,
|
|
{
|
|
action: 'command',
|
|
command: 'oxide.reload FurnaceSplitter',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
);
|
|
|
|
// Mark this config as active, deactivate others
|
|
await this.furnaceRepo.update({ license_id: licenseId }, { is_active: false });
|
|
await this.furnaceRepo.update(
|
|
{ id: configId, license_id: licenseId },
|
|
{ is_active: true, updated_at: new Date() },
|
|
);
|
|
|
|
return {
|
|
success: true,
|
|
message: `Config "${config.config_name}" deployed to server`,
|
|
config_name: config.config_name,
|
|
};
|
|
} catch (error) {
|
|
this.logger.error(`Failed to deploy furnace splitter config: ${(error as Error).message}`);
|
|
throw new HttpException(
|
|
'Failed to deploy furnace splitter config — agent may be offline',
|
|
HttpStatus.SERVICE_UNAVAILABLE,
|
|
);
|
|
}
|
|
}
|
|
|
|
/** Import FurnaceSplitter.json from game server via NATS */
|
|
async importFromServer(licenseId: string, configName: string, description?: string) {
|
|
try {
|
|
// Read FurnaceSplitter.json from server via file manager NATS
|
|
const response = await this.natsService.request(
|
|
`corrosion.${licenseId}.files.cmd`,
|
|
{
|
|
func: 'fm_preview',
|
|
path: 'server://oxide/config/FurnaceSplitter.json',
|
|
},
|
|
30000,
|
|
);
|
|
|
|
if (!response) {
|
|
throw new HttpException(
|
|
'No response from agent — it may be offline',
|
|
HttpStatus.SERVICE_UNAVAILABLE,
|
|
);
|
|
}
|
|
|
|
// Parse the response content as JSON
|
|
const responseData = response as Record<string, any>;
|
|
let configData: Record<string, any>;
|
|
|
|
if (typeof responseData.content === 'string') {
|
|
configData = JSON.parse(responseData.content);
|
|
} else if (typeof responseData.content === 'object') {
|
|
configData = responseData.content;
|
|
} else {
|
|
throw new HttpException(
|
|
'Unexpected response format from agent',
|
|
HttpStatus.BAD_GATEWAY,
|
|
);
|
|
}
|
|
|
|
// Create new furnace splitter config row
|
|
const config = this.furnaceRepo.create({
|
|
license_id: licenseId,
|
|
config_name: configName,
|
|
description: description || 'Imported from server',
|
|
config_data: configData,
|
|
});
|
|
const saved = await this.furnaceRepo.save(config);
|
|
|
|
return { config: saved };
|
|
} catch (error) {
|
|
if (error instanceof HttpException) throw error;
|
|
this.logger.error(`Failed to import furnace splitter config from server: ${(error as Error).message}`);
|
|
throw new HttpException(
|
|
'Failed to import furnace splitter config — agent may be offline',
|
|
HttpStatus.SERVICE_UNAVAILABLE,
|
|
);
|
|
}
|
|
}
|
|
}
|