feat(plugins): plugin system scaffold — registry tables + admin panel

First slice of the plugin system. Lays down the data model and a read-only
admin surface; nothing executes yet.

- Migration 155: plugins, plugin_meta_migrations, plugin_error_log and
  plugin_settings_fields tables. Plugin data will live in a per-plugin sqlite
  file under /plugins-data, never in these tables.
- New Nest module server/src/nest/plugins with GET /api/admin/plugins
  (admin-gated, returns the installed list + the runtime-enabled flag).
- TREK_PLUGINS_ENABLED kill switch (config.pluginsEnabled), off by default.
- Admin → Plugins tab with a read-only panel: installed list, status badges,
  and a clear banner when the runtime is disabled by server config.
- i18n keys for the tab and panel across all locales.

Install, activation, the isolated runtime and the registry browser follow in
later slices.
This commit is contained in:
mauriceboe
2026-07-03 15:27:11 +02:00
parent 452360683c
commit 252eae368d
31 changed files with 555 additions and 2 deletions
@@ -0,0 +1,24 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { PluginsService } from './plugins.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { AdminGuard } from '../auth/admin.guard';
/**
* /api/admin/plugins — admin-only plugin control surface (#plugins).
*
* M0 scaffold: read-only listing + the runtime-enabled flag. Install, activate,
* disable, uninstall and the registry browser are added in later milestones.
* Admin-gated like the rest of /api/admin. The proxy namespace /api/plugins/:id
* is reserved for the (future) runtime and is intentionally NOT part of this
* admin controller.
*/
@Controller('api/admin/plugins')
@UseGuards(JwtAuthGuard, AdminGuard)
export class PluginsController {
constructor(private readonly plugins: PluginsService) {}
@Get()
list() {
return this.plugins.list();
}
}
+14
View File
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { PluginsController } from './plugins.controller';
import { PluginsService } from './plugins.service';
/**
* Plugin system (#plugins). M0 registers only the read side; the isolated
* runtime supervisor, install pipeline and registry service are added in later
* milestones under this same module.
*/
@Module({
controllers: [PluginsController],
providers: [PluginsService],
})
export class PluginsModule {}
@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { db } from '../../db/database';
import { pluginsEnabled } from '../../config';
/**
* Read side of the plugin system (#plugins), M0 scaffold. Lists installed
* plugins from the `plugins` registry table and reports whether the runtime is
* enabled (TREK_PLUGINS_ENABLED). No execution here — the isolated runtime,
* install pipeline and registry fetch land in later milestones.
*/
export interface PluginListItem {
id: string;
name: string;
description: string | null;
type: string;
icon: string | null;
version: string | null;
status: string;
reviewed_at: string | null;
source_repo: string | null;
}
@Injectable()
export class PluginsService {
list(): { enabled: boolean; plugins: PluginListItem[] } {
const plugins = db
.prepare(
`SELECT id, name, description, type, icon, version, status, reviewed_at, source_repo
FROM plugins
ORDER BY sort_order, name`,
)
.all() as PluginListItem[];
return { enabled: pluginsEnabled(), plugins };
}
}