web3.db-fileconnector
Version:
GraphQL System Built on web3 technologies. Web3.DB is an open database built on top of web3 technologies. It is a decentralized, open-source database that allows users to store and query data in a secure and efficient manner. The system is designed to be
391 lines (332 loc) • 11.5 kB
JavaScript
// Plugin Registry and Management API
// This service allows users to submit, validate, and manage plugins
import express from "express";
import multer from "multer";
import fs from "fs";
import path from "path";
import { exec } from "child_process";
import util from "util";
import { v4 as uuidv4 } from "uuid";
import extractZip from "extract-zip";
import rimraf from "rimraf";
import jwt from "jsonwebtoken";
import { requireAuth } from "../../middleware/auth.js";
import logger from "../../logger/index.js";
const router = express.Router();
const execPromise = util.promisify(exec);
// Plugin registry path
const PLUGINS_DIR = path.join(process.cwd(), "server", "plugins");
const TEMP_DIR = path.join(process.cwd(), "temp", "plugins");
const REGISTRY_PATH = path.join(PLUGINS_DIR, "registry.json");
// Ensure temp directory exists
if (!fs.existsSync(TEMP_DIR)) {
fs.mkdirSync(TEMP_DIR, { recursive: true });
}
// Configure upload middleware
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, TEMP_DIR);
},
filename: (req, file, cb) => {
const uniqueId = uuidv4();
cb(null, `${uniqueId}-${file.originalname}`);
},
});
const upload = multer({
storage,
fileFilter: (req, file, cb) => {
// Only accept zip files
if (
file.mimetype === "application/zip" ||
file.originalname.endsWith(".zip")
) {
cb(null, true);
} else {
cb(new Error("Only ZIP files are allowed"));
}
},
limits: {
fileSize: 5 * 1024 * 1024, // 5MB max file size
},
});
/**
* Get all registered plugins
*
* GET /api/plugins/registry
*/
router.get("/registry", requireAuth, async (req, res) => {
try {
// Check if registry file exists
if (!fs.existsSync(REGISTRY_PATH)) {
return res.json({ plugins: {} });
}
// Read registry file
const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, "utf8"));
return res.json(registry);
} catch (error) {
logger.error("Error retrieving plugin registry:", error);
return res
.status(500)
.json({ error: "Failed to retrieve plugin registry" });
}
});
/**
* Get plugin details
*
* GET /api/plugins/registry/:id
*/
router.get("/registry/:id", requireAuth, async (req, res) => {
try {
const pluginId = req.params.id;
// Check if registry file exists
if (!fs.existsSync(REGISTRY_PATH)) {
return res.status(404).json({ error: "Plugin registry not found" });
}
// Read registry file
const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, "utf8"));
// Check if plugin exists in registry
if (!registry.plugins || !registry.plugins[pluginId]) {
return res.status(404).json({ error: "Plugin not found in registry" });
}
return res.json({ plugin: registry.plugins[pluginId] });
} catch (error) {
logger.error("Error retrieving plugin details:", error);
return res.status(500).json({ error: "Failed to retrieve plugin details" });
}
});
/**
* Upload and validate a new plugin
*
* POST /api/plugins/registry/submit
* Body: multipart/form-data with 'plugin' field containing ZIP file
* Optional query param: ?install=true to install the plugin if validation passes
*/
router.post(
"/registry/submit",
requireAuth,
upload.single("plugin"),
async (req, res) => {
let extractedPath = "";
try {
if (!req.file) {
return res.status(400).json({ error: "No plugin file uploaded" });
}
const uploadedFilePath = req.file.path;
const extractDir = path.join(TEMP_DIR, uuidv4());
// Create extraction directory
fs.mkdirSync(extractDir, { recursive: true });
// Extract the ZIP file
await extractZip(uploadedFilePath, { dir: extractDir });
extractedPath = extractDir;
// Check the extracted files to find the plugin directory
// Plugin should contain at least settings.json
const files = fs.readdirSync(extractDir);
let pluginDir = extractDir;
// If the ZIP contains a single directory, use that as the plugin directory
if (
files.length === 1 &&
fs.statSync(path.join(extractDir, files[0])).isDirectory()
) {
pluginDir = path.join(extractDir, files[0]);
}
// Check if settings.json exists
const settingsPath = path.join(pluginDir, "settings.json");
if (!fs.existsSync(settingsPath)) {
return res.status(400).json({
error: "Invalid plugin: settings.json not found",
});
}
// Read the settings.json to get plugin ID
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
const pluginId = settings.id;
if (!pluginId) {
return res.status(400).json({
error: "Invalid plugin: missing plugin ID in settings.json",
});
}
// Run validator script on the extracted plugin
const { stdout, stderr } = await execPromise(
`python ${path.join(process.cwd(), "scripts", "plugin_validator.py")} --path "${pluginDir}"`
);
// Parse validation results
const validationResults = {
pluginId,
pluginName: settings.name,
validationOutput: stdout,
validationErrors: stderr,
passed:
!stderr.includes("FAILED") &&
stdout.includes("passed all validation checks"),
};
// If validation passed and install=true, install the plugin
if (validationResults.passed && req.query.install === "true") {
const targetDir = path.join(PLUGINS_DIR, pluginId);
// Check if plugin already exists
if (fs.existsSync(targetDir)) {
// Backup existing plugin
const backupDir = path.join(
PLUGINS_DIR,
`${pluginId}-backup-${Date.now()}`
);
fs.renameSync(targetDir, backupDir);
}
// Copy files to plugins directory
fs.mkdirSync(targetDir, { recursive: true });
copyRecursive(pluginDir, targetDir);
// Add to registry
await execPromise(
`python ${path.join(process.cwd(), "scripts", "plugin_validator.py")} --id "${pluginId}" --register`
);
validationResults.installed = true;
}
return res.json(validationResults);
} catch (error) {
logger.error("Error processing plugin submission:", error);
return res.status(500).json({
error: "Failed to process plugin submission",
details: error.message,
});
} finally {
// Clean up uploaded file and extracted files
if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}
if (extractedPath && fs.existsSync(extractedPath)) {
rimraf.sync(extractedPath);
}
}
}
);
/**
* Install a validated plugin
*
* POST /api/plugins/registry/install/:id
*/
router.post("/registry/install/:id", requireAuth, async (req, res) => {
try {
const pluginId = req.params.id;
// Check if plugin exists in temp directory
const tempPluginPath = path.join(TEMP_DIR, pluginId);
if (!fs.existsSync(tempPluginPath)) {
return res
.status(404)
.json({ error: "Plugin not found in temporary storage" });
}
// Validate the plugin again
const { stdout, stderr } = await execPromise(
`python ${path.join(process.cwd(), "scripts", "plugin_validator.py")} --path "${tempPluginPath}"`
);
// Check if validation passed
const passed =
!stderr.includes("FAILED") &&
stdout.includes("passed all validation checks");
if (!passed) {
return res.status(400).json({
error: "Plugin failed validation",
validationOutput: stdout,
validationErrors: stderr,
});
}
// Install the plugin
const targetDir = path.join(PLUGINS_DIR, pluginId);
// Check if plugin already exists
if (fs.existsSync(targetDir)) {
// Backup existing plugin
const backupDir = path.join(
PLUGINS_DIR,
`${pluginId}-backup-${Date.now()}`
);
fs.renameSync(targetDir, backupDir);
}
// Copy files to plugins directory
fs.mkdirSync(targetDir, { recursive: true });
copyRecursive(tempPluginPath, targetDir);
// Add to registry
await execPromise(
`python ${path.join(process.cwd(), "scripts", "plugin_validator.py")} --id "${pluginId}" --register`
);
// Clean up temp files
rimraf.sync(tempPluginPath);
return res.json({
success: true,
message: `Plugin ${pluginId} installed successfully`,
});
} catch (error) {
logger.error("Error installing plugin:", error);
return res.status(500).json({
error: "Failed to install plugin",
details: error.message,
});
}
});
/**
* Uninstall a plugin
*
* DELETE /api/plugins/registry/:id
*/
router.delete("/registry/:id", requireAuth, async (req, res) => {
try {
const pluginId = req.params.id;
const pluginDir = path.join(PLUGINS_DIR, pluginId);
// Check if plugin exists
if (!fs.existsSync(pluginDir)) {
return res.status(404).json({ error: "Plugin not found" });
}
// Create backup before removing
const backupDir = path.join(
PLUGINS_DIR,
`${pluginId}-backup-${Date.now()}`
);
fs.renameSync(pluginDir, backupDir);
// Update registry
if (fs.existsSync(REGISTRY_PATH)) {
const registry = JSON.parse(fs.readFileSync(REGISTRY_PATH, "utf8"));
if (registry.plugins && registry.plugins[pluginId]) {
delete registry.plugins[pluginId];
fs.writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
}
}
return res.json({
success: true,
message: `Plugin ${pluginId} uninstalled successfully`,
backup: backupDir,
});
} catch (error) {
logger.error("Error uninstalling plugin:", error);
return res.status(500).json({
error: "Failed to uninstall plugin",
details: error.message,
});
}
});
/**
* Recursively copy files from source to target
*/
function copyRecursive(source, target) {
// Create target directory if it doesn't exist
if (!fs.existsSync(target)) {
fs.mkdirSync(target, { recursive: true });
}
// Copy files and subdirectories
const entries = fs.readdirSync(source, { withFileTypes: true });
for (const entry of entries) {
const sourcePath = path.join(source, entry.name);
const targetPath = path.join(target, entry.name);
if (entry.isDirectory()) {
copyRecursive(sourcePath, targetPath);
} else {
fs.copyFileSync(sourcePath, targetPath);
}
}
}
export default router;
// In a JavaScript project, you can use the following npm scripts in your package.json:
// 1. To automatically fix linting errors:
// "lint:fix": "eslint --fix ."
// 2. To run a check without fixing:
// "lint": "eslint ."
// 3. To specifically target certain files or directories:
// "lint:server": "eslint server/ --fix"
// 4. You can also integrate Prettier with ESLint:
// "format": "prettier --write ."
// "format:check": "prettier --check ."