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:
@@ -3,10 +3,39 @@ import { ref } from 'vue'
|
||||
import { useApi } from '@/composables/useApi'
|
||||
import type { PluginEntry } from '@/types'
|
||||
|
||||
export interface UmodPlugin {
|
||||
name: string
|
||||
title: string
|
||||
slug: string
|
||||
author: string
|
||||
description: string
|
||||
downloads: number
|
||||
downloads_shortened: string
|
||||
download_url: string
|
||||
latest_release_version: string
|
||||
latest_release_version_formatted: string
|
||||
icon_url: string
|
||||
url: string
|
||||
tags_all: string
|
||||
watchers_shortened: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface UmodBrowseResult {
|
||||
current_page: number
|
||||
data: UmodPlugin[]
|
||||
last_page: number
|
||||
per_page: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export const usePluginStore = defineStore('plugins', () => {
|
||||
const plugins = ref<PluginEntry[]>([])
|
||||
const searchResults = ref<any[]>([])
|
||||
const browseResults = ref<UmodBrowseResult | null>(null)
|
||||
const isLoading = ref(false)
|
||||
const isBrowseLoading = ref(false)
|
||||
const api = useApi()
|
||||
|
||||
async function fetchPlugins() {
|
||||
@@ -18,7 +47,7 @@ export const usePluginStore = defineStore('plugins', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function installPlugin(data: { plugin_name: string; source: string }) {
|
||||
async function installPlugin(data: { plugin_name: string; umod_slug?: string; source: string }) {
|
||||
await api.post('/plugins/install', data)
|
||||
await fetchPlugins()
|
||||
}
|
||||
@@ -37,6 +66,7 @@ export const usePluginStore = defineStore('plugins', () => {
|
||||
await fetchPlugins()
|
||||
}
|
||||
|
||||
// Legacy — kept for backwards compatibility, routes to browse
|
||||
async function searchPlugins(query: string) {
|
||||
isLoading.value = true
|
||||
try {
|
||||
@@ -46,15 +76,38 @@ export const usePluginStore = defineStore('plugins', () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function browseUmod(query: string, page = 1, sort = 'downloads') {
|
||||
isBrowseLoading.value = true
|
||||
try {
|
||||
const params = new URLSearchParams({ page: String(page), sort })
|
||||
if (query.trim()) params.set('query', query.trim())
|
||||
browseResults.value = await api.get<UmodBrowseResult>(`/plugins/browse?${params.toString()}`)
|
||||
} finally {
|
||||
isBrowseLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadPlugin(file: File): Promise<PluginEntry> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const result = await api.upload<PluginEntry>('/plugins/upload', formData)
|
||||
await fetchPlugins()
|
||||
return result
|
||||
}
|
||||
|
||||
return {
|
||||
plugins,
|
||||
searchResults,
|
||||
browseResults,
|
||||
isLoading,
|
||||
isBrowseLoading,
|
||||
fetchPlugins,
|
||||
installPlugin,
|
||||
uninstallPlugin,
|
||||
reloadPlugin,
|
||||
updatePluginConfig,
|
||||
searchPlugins,
|
||||
browseUmod,
|
||||
uploadPlugin,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user