import { Controller, Post, Get, Put, Body, Param, Query } from '@nestjs/common'; import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; import { CurrentTenant } from '../../common/decorators/current-tenant.decorator'; import { RequirePermission } from '../../common/decorators/require-permission.decorator'; import { InstancesService, LifecycleFunc } from './instances.service'; @ApiTags('instances') @ApiBearerAuth() @Controller('instances') export class InstancesController { constructor(private readonly instances: InstancesService) {} @Post(':id/lifecycle') @RequirePermission('server.manage') @ApiOperation({ summary: 'Send a lifecycle command to a game instance (start/stop/restart/status/steam_update)' }) async lifecycle( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { action: LifecycleFunc }, ) { return this.instances.lifecycle(licenseId, id, body.action); } @Post(':id/rcon') @RequirePermission('server.console') @ApiOperation({ summary: 'Send an RCON/console command to a game instance' }) async rcon( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { command: string }, ) { return this.instances.rcon(licenseId, id, body.command); } @Get(':id/files') @RequirePermission('files.view') @ApiOperation({ summary: 'List a directory in the instance (jailed to its root)' }) async listFiles( @CurrentTenant() licenseId: string, @Param('id') id: string, @Query('path') path?: string, ) { return this.instances.listFiles(licenseId, id, path ?? ''); } @Get(':id/file') @RequirePermission('files.view') @ApiOperation({ summary: 'Read a text file from the instance (jailed, 5 MiB cap)' }) async readFile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Query('path') path: string, ) { return this.instances.readFile(licenseId, id, path); } @Put(':id/file') @RequirePermission('files.manage') @ApiOperation({ summary: 'Write a text file in the instance (jailed)' }) async writeFile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string; content: string }, ) { return this.instances.writeFile(licenseId, id, body.path, body.content ?? ''); } @Post(':id/files/delete') @RequirePermission('files.manage') @ApiOperation({ summary: 'Delete a file or directory (jailed)' }) async deleteFile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string }, ) { return this.instances.deleteFile(licenseId, id, body.path); } @Post(':id/files/rename') @RequirePermission('files.manage') @ApiOperation({ summary: 'Rename a file/directory within its parent (jailed)' }) async renameFile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string; name: string }, ) { return this.instances.renameFile(licenseId, id, body.path, body.name); } @Post(':id/files/mkdir') @RequirePermission('files.manage') @ApiOperation({ summary: 'Create a directory (jailed)' }) async mkdir( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string }, ) { return this.instances.mkdir(licenseId, id, body.path); } @Post(':id/files/mkfile') @RequirePermission('files.manage') @ApiOperation({ summary: 'Create an empty file (jailed)' }) async mkfile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string }, ) { return this.instances.mkfile(licenseId, id, body.path); } @Post(':id/files/move') @RequirePermission('files.manage') @ApiOperation({ summary: 'Move a file/directory (jailed)' }) async moveFile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string; dest: string }, ) { return this.instances.moveFile(licenseId, id, body.path, body.dest); } @Post(':id/files/copy') @RequirePermission('files.manage') @ApiOperation({ summary: 'Copy a file/directory (jailed)' }) async copyFile( @CurrentTenant() licenseId: string, @Param('id') id: string, @Body() body: { path: string; dest: string }, ) { return this.instances.copyFile(licenseId, id, body.path, body.dest); } }