recoder-code
Version:
🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!
561 lines • 24.6 kB
JavaScript
"use strict";
/**
* Package Routes
* NPM-compatible package registry endpoints
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const container_1 = require("../container");
const PackageService_1 = require("../services/PackageService");
const AuthService_1 = require("../services/AuthService");
const ValidationService_1 = require("../services/ValidationService");
const SecurityService_1 = require("../services/SecurityService");
const StorageService_1 = require("../services/StorageService");
const AnalyticsService_1 = require("../services/AnalyticsService");
const RateLimitService_1 = require("../services/RateLimitService");
const Package_1 = require("../entities/Package");
const ApiKey_1 = require("../entities/ApiKey");
const multer_1 = __importDefault(require("multer"));
const router = (0, express_1.Router)();
const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } }); // 50MB
// Health check endpoint
router.get('/-/health', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
// Basic health checks
const checks = {
database: false,
storage: false,
cache: false,
timestamp: new Date().toISOString()
};
try {
// Test database connection
await packageService.getPackageCount();
checks.database = true;
}
catch (e) {
console.warn('Database health check failed:', e);
}
try {
// Test storage connection
const storageService = container_1.Container.get(StorageService_1.StorageService);
await storageService.testConnection();
checks.storage = true;
}
catch (e) {
console.warn('Storage health check failed:', e);
}
const healthy = checks.database && checks.storage;
res.status(healthy ? 200 : 503).json({
status: healthy ? 'healthy' : 'unhealthy',
checks,
version: process.env.npm_package_version || '1.0.0'
});
}
catch (error) {
res.status(503).json({
status: 'error',
error: 'Health check failed',
timestamp: new Date().toISOString()
});
}
});
// Helper to get typed user from request
const getUser = (req) => getUser(req);
// Middleware
const authenticate = async (req, res, next) => {
try {
const authService = container_1.Container.get(AuthService_1.AuthService);
const rateLimitService = container_1.Container.get(RateLimitService_1.RateLimitService);
const token = req.headers.authorization?.replace('Bearer ', '') ||
req.headers.authorization?.replace('token ', '') ||
req.query.token;
if (!token) {
return res.status(401).json({ error: 'Authentication required' });
}
const result = await authService.validateToken(token);
if (!result.valid) {
return res.status(401).json({ error: 'Invalid authentication token' });
}
// Check rate limits
const rateLimitResult = await rateLimitService.getUserRateLimit(result.user.id);
if (!rateLimitResult.allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
req.user = result.user;
req.apiKey = result.apiKey;
next();
}
catch (error) {
res.status(500).json({ error: 'Authentication failed' });
}
};
const requireScope = (scope) => {
return (req, res, next) => {
if (!req.apiKey?.hasScope(scope)) {
return res.status(403).json({ error: `Insufficient permissions: ${scope} required` });
}
next();
};
};
// NPM Registry Compatibility Routes
// Search packages (CLI compatibility endpoint) - MUST be before /:name route
router.get('/search', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const { q = '' } = req.query;
const searchOptions = {
query: q,
limit: 20,
offset: 0,
sortBy: 'relevance',
sortOrder: 'desc'
};
const results = await packageService.searchPackages(searchOptions);
// CLI expects results array
const response = {
results: results.packages.map(pkg => ({
name: pkg.name,
version: pkg.latest_version,
description: pkg.description,
rating: pkg.quality_metrics?.code_quality || 4.5,
downloads: pkg.quality_metrics?.popularity || 1000
}))
};
res.json(response);
}
catch (error) {
console.error('Plugin search error:', error);
res.status(500).json({
error: 'Search failed',
results: []
});
}
});
// Get package metadata (NPM format)
router.get('/:name', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService);
const packageName = decodeURIComponent(req.params.name);
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check visibility
if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) {
return res.status(404).json({ error: 'Package not found' });
}
// Track package view
await analyticsService.trackPackageView(pkg.id, getUser(req)?.id);
// Return NPM-compatible format
const npmData = pkg.toNpmFormat();
// Add versions
const versions = await packageService.getPackageVersions(pkg.id);
npmData.versions = {};
npmData.time = { created: pkg.created_at.toISOString(), modified: pkg.updated_at.toISOString() };
for (const version of versions) {
npmData.versions[version.version] = version.toNpmFormat();
npmData.time[version.version] = version.published_at?.toISOString() || version.created_at.toISOString();
}
res.json(npmData);
}
catch (error) {
console.error('Error getting package:', error);
res.status(500).json({
error: 'Failed to get package',
code: 'PACKAGE_FETCH_ERROR',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// Get specific package version
router.get('/:name/:version', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService);
const packageName = decodeURIComponent(req.params.name);
const version = req.params.version;
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check visibility
if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) {
return res.status(404).json({ error: 'Package not found' });
}
const packageVersion = await packageService.getPackageVersion(pkg.id, version);
if (!packageVersion) {
return res.status(404).json({ error: 'Version not found' });
}
// Track version view
await analyticsService.trackVersionView(packageVersion.id, getUser(req)?.id, req.ip);
res.json(packageVersion.toNpmFormat());
}
catch (error) {
console.error('Error getting package version:', error);
res.status(500).json({
error: 'Failed to get package version',
code: 'VERSION_FETCH_ERROR',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// Download package tarball
router.get('/:name/-/:filename', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const storageService = container_1.Container.get(StorageService_1.StorageService);
const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService);
const packageName = decodeURIComponent(req.params.name);
const filename = req.params.filename;
// Extract version from filename (format: package-version.tgz)
const versionMatch = filename.match(/^.*?-(\d+\.\d+\.\d+.*?)\.tgz$/);
if (!versionMatch) {
return res.status(400).json({ error: 'Invalid filename format' });
}
const version = versionMatch[1];
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check visibility
if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) {
return res.status(404).json({ error: 'Package not found' });
}
const packageVersion = await packageService.getPackageVersion(pkg.id, version);
if (!packageVersion) {
return res.status(404).json({ error: 'Version not found' });
}
// Download from storage
const downloadResult = await storageService.downloadPackage(packageName, version);
if (!downloadResult.success) {
return res.status(404).json({ error: 'Package file not found' });
}
// Track download
await analyticsService.trackDownload(pkg.id, packageVersion.version, getUser(req)?.id);
// Update download counts
await packageService.incrementDownloadCount(pkg.id, packageVersion.id);
// Set headers
if (!downloadResult.data) {
return res.status(404).json({ error: 'Package file not found' });
}
res.set({
'Content-Type': 'application/octet-stream',
'Content-Length': downloadResult.data.length.toString(),
'Content-Disposition': `attachment; filename="${filename}"`,
'Cache-Control': 'public, max-age=31536000',
'ETag': downloadResult.metadata?.etag
});
res.send(downloadResult.data);
}
catch (error) {
console.error('Error downloading package:', error);
res.status(500).json({
error: 'Failed to download package',
code: 'DOWNLOAD_ERROR',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// Publish package
router.put('/:name', authenticate, requireScope(ApiKey_1.ApiKeyScope.PUBLISH), upload.single('package'), async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const validationService = container_1.Container.get(ValidationService_1.ValidationService);
const securityService = container_1.Container.get(SecurityService_1.SecurityService);
const storageService = container_1.Container.get(StorageService_1.StorageService);
const packageName = decodeURIComponent(req.params.name);
// Handle both file upload and JSON body
let packageBuffer;
let packageJson;
if (req.file) {
// Multipart upload
packageBuffer = req.file.buffer;
}
else if (req.body._attachments) {
// NPM publish format
const attachments = req.body._attachments;
const attachmentKey = Object.keys(attachments)[0];
if (!attachmentKey) {
return res.status(400).json({ error: 'No package attachment found' });
}
const attachment = attachments[attachmentKey];
packageBuffer = Buffer.from(attachment.data, 'base64');
packageJson = req.body.versions?.[Object.keys(req.body.versions)[0]];
}
else {
return res.status(400).json({ error: 'No package data provided' });
}
if (!packageBuffer) {
return res.status(400).json({ error: 'Empty package data' });
}
// Validate package
const validationResult = await validationService.validatePackage(packageBuffer, packageName);
if (!validationResult.valid) {
return res.status(400).json({
error: 'Package validation failed',
errors: validationResult.errors,
warnings: validationResult.warnings
});
}
const version = packageJson?.version || (validationResult.size_analysis && validationResult.size_analysis['package.json']?.version);
if (!version) {
return res.status(400).json({ error: 'Package version not found' });
}
// Check if package/version already exists
const existingPackage = await packageService.getPackage(packageName);
if (existingPackage) {
const existingVersion = await packageService.getPackageVersion(existingPackage.id, version);
if (existingVersion) {
return res.status(409).json({ error: 'Version already exists' });
}
// Check permissions
if (!getUser(req).canModifyPackage(existingPackage)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
}
// Security scan
const fakePackageVersion = {
version,
package: existingPackage || { name: packageName },
}; // Cast as any to satisfy the type, since only name/version are used in logging
const securityResult = await securityService.scanPackage(packageBuffer, fakePackageVersion);
if (securityResult.status === 'critical') {
return res.status(400).json({
error: 'Security scan failed',
details: securityResult.threats
});
}
// Upload to storage
const uploadResult = await storageService.uploadPackage(packageName, version, packageBuffer, 'application/octet-stream');
if (!uploadResult.success) {
return res.status(500).json({ error: 'Failed to store package' });
}
// Create/update package and version
const result = await packageService.publishPackage(packageJson || { name: packageName, version }, getUser(req), req.file?.buffer);
if (!result.success) {
return res.status(500).json({ error: result.errors?.[0] || 'Package publishing failed' });
}
if (!result.package || !result.version) {
return res.status(500).json({ error: 'Package publishing failed: missing package or version data' });
}
res.json({
success: true,
package: result.package.toApiFormat(),
version: result.version.toApiFormat()
});
}
catch (error) {
console.error('Error publishing package:', error);
res.status(500).json({
error: 'Failed to publish package',
code: 'PUBLISH_ERROR',
details: process.env.NODE_ENV === 'development' ? error.message : undefined
});
}
});
// Unpublish package version
router.delete('/:name/-/:version', authenticate, requireScope(ApiKey_1.ApiKeyScope.UNPUBLISH), async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const storageService = container_1.Container.get(StorageService_1.StorageService);
const packageName = decodeURIComponent(req.params.name);
const version = req.params.version;
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check permissions
if (!getUser(req).canModifyPackage(pkg)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
const packageVersion = await packageService.getPackageVersion(pkg.id, version);
if (!packageVersion) {
return res.status(404).json({ error: 'Version not found' });
}
// Delete from storage
await storageService.deletePackage(packageName, version);
// Remove version from database
const result = await packageService.unpublishVersion(packageName, version, getUser(req));
if (!result.success) {
return res.status(500).json({ error: result.error });
}
res.json({ success: true, message: 'Version unpublished successfully' });
}
catch (error) {
res.status(500).json({ error: 'Failed to unpublish version' });
}
});
// Deprecate package version
router.post('/:name/:version/deprecate', authenticate, requireScope(ApiKey_1.ApiKeyScope.DEPRECATE), async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const packageName = decodeURIComponent(req.params.name);
const version = req.params.version;
const { message } = req.body;
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check permissions
if (!getUser(req).canModifyPackage(pkg)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
const result = await packageService.deprecateVersion(pkg.id, version, message);
if (!result.success) {
return res.status(500).json({ error: result.error });
}
res.json({ success: true, message: 'Version deprecated successfully' });
}
catch (error) {
res.status(500).json({ error: 'Failed to deprecate version' });
}
});
// Search packages (NPM compatibility)
router.get('/-/search', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const { text = '', size = 20, from = 0, quality = 0.5, popularity = 0.5, maintenance = 0.5 } = req.query;
const searchOptions = {
query: text,
limit: Math.min(parseInt(size) || 20, 100),
offset: parseInt(from) || 0,
sortBy: 'relevance',
sortOrder: 'desc'
};
const results = await packageService.searchPackages(searchOptions);
// NPM search format
const response = {
objects: results.packages.map(pkg => ({
package: {
name: pkg.name,
scope: pkg.scope,
version: pkg.latest_version,
description: pkg.description,
keywords: pkg.keywords || [],
date: pkg.last_published?.toISOString(),
links: {
npm: `https://npmjs.com/package/${pkg.name}`,
homepage: pkg.homepage,
repository: pkg.repository?.url,
bugs: pkg.bugs?.url
},
author: pkg.author,
publisher: pkg.maintainers?.[0],
maintainers: pkg.maintainers || []
},
score: {
final: pkg.quality_metrics?.code_quality || 0,
detail: {
quality: pkg.quality_metrics?.code_quality || 0,
popularity: pkg.quality_metrics?.popularity || 0,
maintenance: pkg.quality_metrics?.maintenance || 0
}
},
searchScore: 1.0
})),
total: results.total,
time: new Date().toISOString()
};
res.json(response);
}
catch (error) {
res.status(500).json({ error: 'Search failed' });
}
});
// Get package statistics
router.get('/:name/-/stats', async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService);
const packageName = decodeURIComponent(req.params.name);
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check visibility
if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) {
return res.status(404).json({ error: 'Package not found' });
}
const stats = await analyticsService.getPackageStats(pkg.id);
res.json({
package: pkg.name,
downloads: stats.downloads,
versions: stats.versions,
dependents: stats.dependents,
last_updated: pkg.updated_at
});
}
catch (error) {
res.status(500).json({ error: 'Failed to get package stats' });
}
});
// Add package collaborator
router.put('/:name/-/collaborators/:username', authenticate, async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const packageName = decodeURIComponent(req.params.name);
const username = req.params.username;
const { permissions = ['read'] } = req.body;
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check permissions (only owner can add collaborators)
if (pkg.owner_id !== getUser(req).id && !getUser(req).is_admin) {
return res.status(403).json({ error: 'Only package owner can add collaborators' });
}
const result = await packageService.addCollaborator(pkg.id, username, permissions);
if (!result.success) {
return res.status(400).json({ error: result.error });
}
res.json({ success: true, message: 'Collaborator added successfully' });
}
catch (error) {
res.status(500).json({ error: 'Failed to add collaborator' });
}
});
// Remove package collaborator
router.delete('/:name/-/collaborators/:username', authenticate, async (req, res) => {
try {
const packageService = container_1.Container.get(PackageService_1.PackageService);
const packageName = decodeURIComponent(req.params.name);
const username = req.params.username;
const pkg = await packageService.getPackage(packageName);
if (!pkg) {
return res.status(404).json({ error: 'Package not found' });
}
// Check permissions
if (pkg.owner_id !== getUser(req).id && !getUser(req).is_admin) {
return res.status(403).json({ error: 'Only package owner can remove collaborators' });
}
const result = await packageService.removeCollaborator(pkg.name, username, getUser(req));
if (!result.success) {
return res.status(400).json({ error: result.error });
}
res.json({ success: true, message: 'Collaborator removed successfully' });
}
catch (error) {
res.status(500).json({ error: 'Failed to remove collaborator' });
}
});
// Get package download counts
router.get('/:name/-/downloads', async (req, res) => {
try {
const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService);
const packageName = decodeURIComponent(req.params.name);
const { period = 'week', version } = req.query;
const downloads = await analyticsService.getDownloadStats(packageName, period);
res.json(downloads);
}
catch (error) {
res.status(500).json({ error: 'Failed to get download stats' });
}
});
exports.default = router;
//# sourceMappingURL=packages.js.map