/** * StackGalleryRegistry - Main registry class * Integrates smartregistry with Stack.Gallery's auth, storage, and database */ import * as plugins from './plugins.ts'; import { initDb, closeDb, isDbConnected } from './models/db.ts'; import { StackGalleryAuthProvider } from './providers/auth.provider.ts'; import { StackGalleryStorageHooks } from './providers/storage.provider.ts'; import { ApiRouter } from './api/router.ts'; export interface IRegistryConfig { // MongoDB configuration mongoUrl: string; mongoDb: string; // S3 configuration s3Endpoint: string; s3AccessKey: string; s3SecretKey: string; s3Bucket: string; s3Region?: string; // Server configuration host?: string; port?: number; // Registry settings storagePath?: string; enableUpstreamCache?: boolean; upstreamCacheExpiry?: number; // hours // JWT configuration jwtSecret?: string; } export class StackGalleryRegistry { private config: IRegistryConfig; private smartBucket: plugins.smartbucket.SmartBucket | null = null; private smartRegistry: plugins.smartregistry.SmartRegistry | null = null; private authProvider: StackGalleryAuthProvider | null = null; private storageHooks: StackGalleryStorageHooks | null = null; private apiRouter: ApiRouter | null = null; private isInitialized = false; constructor(config: IRegistryConfig) { this.config = { host: '0.0.0.0', port: 3000, storagePath: 'packages', enableUpstreamCache: true, upstreamCacheExpiry: 24, ...config, }; } /** * Initialize the registry */ public async init(): Promise { if (this.isInitialized) return; console.log('[StackGalleryRegistry] Initializing...'); // Initialize MongoDB console.log('[StackGalleryRegistry] Connecting to MongoDB...'); await initDb(this.config.mongoUrl, this.config.mongoDb); console.log('[StackGalleryRegistry] MongoDB connected'); // Initialize S3/SmartBucket console.log('[StackGalleryRegistry] Initializing S3 storage...'); this.smartBucket = new plugins.smartbucket.SmartBucket({ accessKey: this.config.s3AccessKey, accessSecret: this.config.s3SecretKey, endpoint: this.config.s3Endpoint, bucketName: this.config.s3Bucket, }); console.log('[StackGalleryRegistry] S3 storage initialized'); // Initialize auth provider this.authProvider = new StackGalleryAuthProvider(); // Initialize storage hooks this.storageHooks = new StackGalleryStorageHooks({ bucket: this.smartBucket, basePath: this.config.storagePath!, }); // Initialize smartregistry console.log('[StackGalleryRegistry] Initializing smartregistry...'); this.smartRegistry = new plugins.smartregistry.SmartRegistry({ authProvider: this.authProvider, storageHooks: this.storageHooks, storage: { type: 's3', bucket: this.smartBucket, basePath: this.config.storagePath, }, upstreamCache: this.config.enableUpstreamCache ? { enabled: true, expiryHours: this.config.upstreamCacheExpiry, } : undefined, }); console.log('[StackGalleryRegistry] smartregistry initialized'); // Initialize API router console.log('[StackGalleryRegistry] Initializing API router...'); this.apiRouter = new ApiRouter(); console.log('[StackGalleryRegistry] API router initialized'); this.isInitialized = true; console.log('[StackGalleryRegistry] Initialization complete'); } /** * Start the HTTP server */ public async start(): Promise { if (!this.isInitialized) { await this.init(); } const port = this.config.port!; const host = this.config.host!; console.log(`[StackGalleryRegistry] Starting server on ${host}:${port}...`); Deno.serve( { port, hostname: host }, async (request: Request): Promise => { return await this.handleRequest(request); } ); console.log(`[StackGalleryRegistry] Server running on http://${host}:${port}`); } /** * Handle incoming HTTP request */ private async handleRequest(request: Request): Promise { const url = new URL(request.url); const path = url.pathname; // Health check if (path === '/health' || path === '/healthz') { return this.healthCheck(); } // API endpoints (handled by REST API layer) if (path.startsWith('/api/')) { return await this.handleApiRequest(request); } // Registry protocol endpoints // NPM: /-/..., /@scope/package, /package // OCI: /v2/... // Maven: /maven2/... // PyPI: /simple/..., /pypi/... // Cargo: /api/v1/crates/... // Composer: /packages.json, /p/... // RubyGems: /api/v1/gems/..., /gems/... if (this.smartRegistry) { try { return await this.smartRegistry.handleRequest(request); } catch (error) { console.error('[StackGalleryRegistry] Request error:', error); return new Response( JSON.stringify({ error: 'Internal server error' }), { status: 500, headers: { 'Content-Type': 'application/json' }, } ); } } return new Response('Not Found', { status: 404 }); } /** * Handle API requests */ private async handleApiRequest(request: Request): Promise { if (!this.apiRouter) { return new Response( JSON.stringify({ error: 'API router not initialized' }), { status: 503, headers: { 'Content-Type': 'application/json' }, } ); } return await this.apiRouter.handle(request); } /** * Health check endpoint */ private healthCheck(): Response { const healthy = this.isInitialized && isDbConnected(); const status = { status: healthy ? 'healthy' : 'unhealthy', timestamp: new Date().toISOString(), services: { mongodb: isDbConnected() ? 'connected' : 'disconnected', s3: this.smartBucket ? 'initialized' : 'not initialized', registry: this.smartRegistry ? 'initialized' : 'not initialized', }, }; return new Response(JSON.stringify(status), { status: healthy ? 200 : 503, headers: { 'Content-Type': 'application/json' }, }); } /** * Stop the registry */ public async stop(): Promise { console.log('[StackGalleryRegistry] Shutting down...'); await closeDb(); this.isInitialized = false; console.log('[StackGalleryRegistry] Shutdown complete'); } /** * Get the smartregistry instance */ public getSmartRegistry(): plugins.smartregistry.SmartRegistry | null { return this.smartRegistry; } /** * Get the smartbucket instance */ public getSmartBucket(): plugins.smartbucket.SmartBucket | null { return this.smartBucket; } /** * Check if registry is initialized */ public getIsInitialized(): boolean { return this.isInitialized; } } /** * Create registry from environment variables */ export function createRegistryFromEnv(): StackGalleryRegistry { const config: IRegistryConfig = { mongoUrl: Deno.env.get('MONGODB_URL') || 'mongodb://localhost:27017', mongoDb: Deno.env.get('MONGODB_DB') || 'stackgallery', s3Endpoint: Deno.env.get('S3_ENDPOINT') || 'http://localhost:9000', s3AccessKey: Deno.env.get('S3_ACCESS_KEY') || 'minioadmin', s3SecretKey: Deno.env.get('S3_SECRET_KEY') || 'minioadmin', s3Bucket: Deno.env.get('S3_BUCKET') || 'registry', s3Region: Deno.env.get('S3_REGION'), host: Deno.env.get('HOST') || '0.0.0.0', port: parseInt(Deno.env.get('PORT') || '3000', 10), storagePath: Deno.env.get('STORAGE_PATH') || 'packages', enableUpstreamCache: Deno.env.get('ENABLE_UPSTREAM_CACHE') !== 'false', upstreamCacheExpiry: parseInt(Deno.env.get('UPSTREAM_CACHE_EXPIRY') || '24', 10), jwtSecret: Deno.env.get('JWT_SECRET'), }; return new StackGalleryRegistry(config); }