All checks were successful
Test Asgard Runner / test (push) Successful in 3s
- GatherManager: 2-tab editor (Resource Rates with 1x-10x presets, Advanced with Pickup/Quarry/Excavator/Survey modifiers), 9 resource types with slider+number inputs, CRUD + deploy + import via NATS - AutoDoors: Global settings (delay sliders, 6 toggles), 7 door type toggles, permission group overrides table, CRUD + deploy + import - DB: migrations 015 (gather_configs) + 018 (autodoors_configs) - Backend: GatherModule + AutoDoorsModule registered in app.module.ts - Frontend: Pinia stores, Vue views, router routes, sidebar nav items - Icons: Pickaxe (gather), DoorOpen (autodoors) - All type checks pass: tsc + vue-tsc zero errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
6.2 KiB
TypeScript
181 lines
6.2 KiB
TypeScript
import { Injectable, Logger, NotFoundException, HttpException, HttpStatus } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { Repository } from 'typeorm';
|
|
import { GatherConfig } from '../../entities/gather-config.entity';
|
|
import { NatsService } from '../../services/nats.service';
|
|
import { CreateGatherConfigDto } from './dto/create-gather-config.dto';
|
|
import { UpdateGatherConfigDto } from './dto/update-gather-config.dto';
|
|
|
|
@Injectable()
|
|
export class GatherService {
|
|
private readonly logger = new Logger(GatherService.name);
|
|
|
|
constructor(
|
|
@InjectRepository(GatherConfig)
|
|
private readonly gatherRepo: Repository<GatherConfig>,
|
|
private readonly natsService: NatsService,
|
|
) {}
|
|
|
|
/** List configs for a license (summaries — no JSONB) */
|
|
async getConfigs(licenseId: string) {
|
|
const configs = await this.gatherRepo.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.gatherRepo.findOne({
|
|
where: { id: configId, license_id: licenseId },
|
|
});
|
|
if (!config) throw new NotFoundException('Gather config not found');
|
|
return { config };
|
|
}
|
|
|
|
/** Create a new config */
|
|
async createConfig(licenseId: string, dto: CreateGatherConfigDto) {
|
|
const config = this.gatherRepo.create({
|
|
license_id: licenseId,
|
|
config_name: dto.config_name,
|
|
description: dto.description || null,
|
|
config_data: dto.config_data || {},
|
|
});
|
|
const saved = await this.gatherRepo.save(config);
|
|
return { config: saved };
|
|
}
|
|
|
|
/** Update an existing config */
|
|
async updateConfig(licenseId: string, configId: string, dto: UpdateGatherConfigDto) {
|
|
const config = await this.gatherRepo.findOne({
|
|
where: { id: configId, license_id: licenseId },
|
|
});
|
|
if (!config) throw new NotFoundException('Gather 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.gatherRepo.save(config);
|
|
return { config: saved };
|
|
}
|
|
|
|
/** Delete a config */
|
|
async deleteConfig(licenseId: string, configId: string) {
|
|
const result = await this.gatherRepo.delete({ id: configId, license_id: licenseId });
|
|
if (result.affected === 0) throw new NotFoundException('Gather config not found');
|
|
return { deleted: true };
|
|
}
|
|
|
|
/** Deploy config to game server via NATS */
|
|
async applyToServer(licenseId: string, configId: string) {
|
|
const config = await this.gatherRepo.findOne({
|
|
where: { id: configId, license_id: licenseId },
|
|
});
|
|
if (!config) throw new NotFoundException('Gather config not found');
|
|
|
|
const jsonString = JSON.stringify(config.config_data, null, 2);
|
|
|
|
try {
|
|
// Write GatherManager.json via file manager NATS
|
|
await this.natsService.request(
|
|
`corrosion.${licenseId}.files.cmd`,
|
|
{
|
|
func: 'fm_save',
|
|
path: 'server://oxide/config/GatherManager.json',
|
|
content: jsonString,
|
|
},
|
|
30000,
|
|
);
|
|
|
|
// Reload GatherManager plugin via RCON
|
|
await this.natsService.publish(
|
|
`corrosion.${licenseId}.cmd.server`,
|
|
{
|
|
action: 'command',
|
|
command: 'oxide.reload GatherManager',
|
|
timestamp: new Date().toISOString(),
|
|
},
|
|
);
|
|
|
|
// Mark this config as active, deactivate others
|
|
await this.gatherRepo.update({ license_id: licenseId }, { is_active: false });
|
|
await this.gatherRepo.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 gather config: ${(error as Error).message}`);
|
|
throw new HttpException(
|
|
'Failed to deploy gather config — agent may be offline',
|
|
HttpStatus.SERVICE_UNAVAILABLE,
|
|
);
|
|
}
|
|
}
|
|
|
|
/** Import GatherManager.json from game server via NATS */
|
|
async importFromServer(licenseId: string, configName: string, description?: string) {
|
|
try {
|
|
// Read GatherManager.json from server via file manager NATS
|
|
const response = await this.natsService.request(
|
|
`corrosion.${licenseId}.files.cmd`,
|
|
{
|
|
func: 'fm_preview',
|
|
path: 'server://oxide/config/GatherManager.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 gather config row
|
|
const config = this.gatherRepo.create({
|
|
license_id: licenseId,
|
|
config_name: configName,
|
|
description: description || 'Imported from server',
|
|
config_data: configData,
|
|
});
|
|
const saved = await this.gatherRepo.save(config);
|
|
|
|
return { config: saved };
|
|
} catch (error) {
|
|
if (error instanceof HttpException) throw error;
|
|
this.logger.error(`Failed to import gather config from server: ${(error as Error).message}`);
|
|
throw new HttpException(
|
|
'Failed to import gather config — agent may be offline',
|
|
HttpStatus.SERVICE_UNAVAILABLE,
|
|
);
|
|
}
|
|
}
|
|
}
|