feat: Wire uMod browse proxy and custom plugin upload
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
All checks were successful
Test Asgard Runner / test (push) Successful in 2s
Backend: - GET /plugins/browse proxies uMod search.json filtered to Rust category, with 5-minute in-memory Map cache to avoid hammering the upstream API - POST /plugins/upload accepts .cs files up to 5 MB via multipart, persists to plugin_registry, and dispatches plugin_upload action over NATS so the companion agent can write the file to the game server - Legacy GET /plugins/search stub preserved (now directs callers to /browse) - FileInterceptor + @UploadedFile follow the existing maps upload pattern Frontend: - useApi composable gains upload() method for multipart/form-data requests (omits Content-Type so the browser sets the correct multipart boundary) - plugins store adds browseUmod() calling GET /plugins/browse and uploadPlugin() calling POST /plugins/upload with FormData; UmodPlugin and UmodBrowseResult TypeScript interfaces exported - PluginsView Browse tab now calls browseUmod() through the backend proxy (no cross-origin requests to uMod directly); results show title, downloads_shortened, and latest_release_version_formatted from the real uMod payload - New Upload Custom tab: drag-and-drop or click file input for .cs files, client-side extension/size validation, spinner during upload, success toast + auto-switch to Installed tab on completion Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,19 @@
|
||||
import { Controller, Get, Post, Put, Delete, Body, Param, Query, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery } from '@nestjs/swagger';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
Query,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
UploadedFile,
|
||||
BadRequestException,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation, ApiQuery, ApiConsumes } from '@nestjs/swagger';
|
||||
import { PluginsService } from './plugins.service';
|
||||
import { InstallPluginDto } from './dto/install-plugin.dto';
|
||||
import { UpdatePluginConfigDto } from './dto/update-plugin-config.dto';
|
||||
@@ -57,9 +71,38 @@ export class PluginsController {
|
||||
|
||||
@Get('search')
|
||||
@RequirePermission('plugin.view')
|
||||
@ApiOperation({ summary: 'Search uMod plugin directory' })
|
||||
@ApiOperation({ summary: 'Search uMod plugin directory (legacy stub)' })
|
||||
@ApiQuery({ name: 'q', required: true, example: 'kits' })
|
||||
searchUmod(@Query('q') query: string) {
|
||||
return this.pluginsService.searchUmod(query);
|
||||
}
|
||||
|
||||
@Get('browse')
|
||||
@RequirePermission('plugin.view')
|
||||
@ApiOperation({ summary: 'Browse uMod plugin directory (proxied)' })
|
||||
@ApiQuery({ name: 'query', required: false, example: 'vanish' })
|
||||
@ApiQuery({ name: 'page', required: false, example: 1 })
|
||||
@ApiQuery({ name: 'sort', required: false, example: 'downloads' })
|
||||
browseUmod(
|
||||
@Query('query') query: string,
|
||||
@Query('page') page: string,
|
||||
@Query('sort') sort: string,
|
||||
) {
|
||||
return this.pluginsService.browseUmod(query, page ? parseInt(page, 10) : 1, sort);
|
||||
}
|
||||
|
||||
@Post('upload')
|
||||
@RequirePermission('plugin.manage')
|
||||
@ApiOperation({ summary: 'Upload a custom .cs plugin file' })
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadPlugin(
|
||||
@CurrentTenant() licenseId: string,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
) {
|
||||
if (!file) {
|
||||
throw new BadRequestException('No file provided');
|
||||
}
|
||||
return this.pluginsService.uploadPlugin(licenseId, file);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user