loic boulet 1 månad sedan
förälder
incheckning
8382de0022

+ 63 - 32
app/init.go

@@ -2,6 +2,7 @@ package app
 
 import (
 	"fmt"
+	"net/http"
 	"time"
 
 	"git.linuxforward.com/byop/byop-engine/auth"
@@ -56,8 +57,8 @@ func (a *App) initCommonServices() error {
 	if err := a.dbManager.Migrate(
 		&models.User{},
 		&models.Client{},
-		&models.App{},
-		&models.Template{},
+		&models.Component{}, // Renamed from App
+		&models.App{},       // Renamed from Template
 		&models.Deployment{},
 		&models.DeployedApp{},
 		&models.DeployedAppResource{},
@@ -92,32 +93,32 @@ func (a *App) initHandlers() error {
 		Handler: clientHandler,
 	}
 
-	// Initialize AppModule
+	// Initialize ComponentModule (formerly AppModule)
+	componentStore := dbstore.NewComponentStore(a.dbManager)
+	componentService := services.NewComponentService(componentStore)
+	componentHandler := handlers.NewComponentHandler(componentService)
+	a.componentModule = &ComponentModule{
+		Store:   componentStore,
+		Service: componentService,
+		Handler: componentHandler,
+	}
+
+	// Initialize AppModule (formerly TemplateModule)
 	appStore := dbstore.NewAppStore(a.dbManager)
 	appService := services.NewAppService(appStore)
-	appHandler := handlers.NewAppHandler(appService)
+	appsHandler := handlers.NewAppsHandler(appService)
 	a.appModule = &AppModule{
 		Store:   appStore,
 		Service: appService,
-		Handler: appHandler,
-	}
-
-	// Initialize TemplateModule
-	templateStore := dbstore.NewTemplateStore(a.dbManager)
-	templateService := services.NewTemplateService(templateStore)
-	templateHandler := handlers.NewTemplateHandler(templateService)
-	a.templateModule = &TemplateModule{
-		Store:   templateStore,
-		Service: templateService,
-		Handler: templateHandler,
+		Handler: appsHandler,
 	}
 
 	// Initialize DeploymentModule
 	deploymentStore := dbstore.NewDeploymentStore(a.dbManager)
 	deploymentService := services.NewDeploymentService(
 		deploymentStore,
+		componentStore,
 		appStore,
-		templateStore,
 		clientStore,
 	)
 	deploymentHandler := handlers.NewDeploymentHandler(deploymentService)
@@ -156,6 +157,27 @@ func (a *App) loadProviders() error {
 }
 
 func (a *App) setupRoutes() {
+	// Add CORS middleware to handle all cross-origin requests
+	a.rtr.Use(func(c *gin.Context) {
+		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
+
+		c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
+		c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
+		// Allow redirects
+		c.Writer.Header().Set("Access-Control-Expose-Headers", "Location")
+		c.Writer.Header().Set("Access-Control-Max-Age", "86400") // Cache preflight response for 24 hours
+		c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
+
+		// Handle preflight OPTIONS requests
+		if c.Request.Method == http.MethodOptions {
+			c.AbortWithStatus(http.StatusNoContent)
+			return
+		}
+
+		c.Next()
+	})
+
 	// API version group
 	v1 := a.rtr.Group("/api/v1")
 
@@ -183,8 +205,8 @@ func (a *App) setupRoutes() {
 
 	// Client routes
 	clients := protected.Group("/clients")
-	clients.GET("/", a.clientModule.Handler.ListClients)
-	clients.POST("/", a.clientModule.Handler.CreateClient)
+	clients.GET("", a.clientModule.Handler.ListClients)
+	clients.POST("", a.clientModule.Handler.CreateClient)
 	clients.GET("/:id", a.clientModule.Handler.GetClient)
 	clients.PUT("/:id", a.clientModule.Handler.UpdateClient)
 	clients.DELETE("/:id", a.clientModule.Handler.DeleteClient)
@@ -192,33 +214,42 @@ func (a *App) setupRoutes() {
 
 	// User routes
 	users := protected.Group("/users")
-	users.GET("/", a.userModule.Handler.ListUsers)
+	users.GET("", a.userModule.Handler.ListUsers)
 	users.GET("/:id", a.userModule.Handler.GetUser)
 	users.PUT("/:id", a.userModule.Handler.UpdateUser)
 	users.DELETE("/:id", a.userModule.Handler.DeleteUser)
 	users.GET("/:id/deployments", a.userModule.Handler.GetUserDeployments)
 
-	// App routes
+	// Component routes (formerly Apps)
+	components := protected.Group("/components")
+	components.GET("", a.componentModule.Handler.ListComponents)
+	components.POST("", a.componentModule.Handler.CreateComponent)
+	components.GET("/:id", a.componentModule.Handler.GetComponent)
+	components.PUT("/:id", a.componentModule.Handler.UpdateComponent)
+	components.DELETE("/:id", a.componentModule.Handler.DeleteComponent)
+	components.GET("/:id/deployments", a.componentModule.Handler.GetComponentDeployments)
+
+	// App routes (formerly Templates)
 	apps := protected.Group("/apps")
-	apps.GET("/", a.appModule.Handler.ListApps)
-	apps.POST("/", a.appModule.Handler.CreateApp)
+	apps.GET("", a.appModule.Handler.ListApps)
+	apps.POST("", a.appModule.Handler.CreateApp)
 	apps.GET("/:id", a.appModule.Handler.GetApp)
 	apps.PUT("/:id", a.appModule.Handler.UpdateApp)
 	apps.DELETE("/:id", a.appModule.Handler.DeleteApp)
 	apps.GET("/:id/deployments", a.appModule.Handler.GetAppDeployments)
 
-	// Template routes
-	templates := protected.Group("/templates")
-	templates.GET("/", a.templateModule.Handler.ListTemplates)
-	templates.POST("/", a.templateModule.Handler.CreateTemplate)
-	templates.GET("/:id", a.templateModule.Handler.GetTemplate)
-	templates.PUT("/:id", a.templateModule.Handler.UpdateTemplate)
-	templates.DELETE("/:id", a.templateModule.Handler.DeleteTemplate)
-	templates.GET("/:id/deployments", a.templateModule.Handler.GetTemplateDeployments)
-
 	// Deployment routes
 	deployments := protected.Group("/deployments")
-	a.deploymentModule.Handler.RegisterRoutes(deployments)
+	deployments.GET("", a.deploymentModule.Handler.ListDeployments)
+	deployments.POST("", a.deploymentModule.Handler.CreateDeployment)
+	deployments.GET("/:id", a.deploymentModule.Handler.GetDeployment)
+	deployments.PUT("/:id", a.deploymentModule.Handler.UpdateDeployment)
+	deployments.DELETE("/:id", a.deploymentModule.Handler.DeleteDeployment)
+	deployments.PUT("/:id/status", a.deploymentModule.Handler.UpdateDeploymentStatus)
+	// deployments.GET("/by-client/:clientId", a.deploymentModule.Handler.GetDeploymentsByClient)
+	// deployments.GET("/by-app/:appId", a.deploymentModule.Handler.GetDeploymentsByTemplate) // Was by-template
+	// deployments.GET("/by-user/:userId", a.deploymentModule.Handler.GetDeploymentsByUser)
+	// // Add other resource routes as needed
 
 	a.entry.Info("Routes configured successfully")
 }

+ 12 - 13
app/server.go

@@ -6,7 +6,6 @@ import (
 	"net/http"
 	"os"
 	"os/signal"
-	"strconv"
 	"syscall"
 	"time"
 
@@ -39,8 +38,8 @@ type App struct {
 	authHandler      *handlers.AuthHandler
 	userModule       *UserModule
 	clientModule     *ClientModule
-	appModule        *AppModule
-	templateModule   *TemplateModule
+	componentModule  *ComponentModule // Renamed from appModule
+	appModule        *AppModule       // Renamed from templateModule
 	deploymentModule *DeploymentModule
 
 	// Resource Handlers
@@ -61,16 +60,16 @@ type ClientModule struct {
 	Handler *handlers.ClientHandler
 }
 
-type AppModule struct {
-	Store   *dbstore.AppStore
-	Service *services.AppService
-	Handler *handlers.AppHandler
+type ComponentModule struct {
+	Store   *dbstore.ComponentStore    // Renamed from AppStore
+	Service *services.ComponentService // Renamed from AppService
+	Handler *handlers.ComponentHandler // Renamed from AppHandler
 }
 
-type TemplateModule struct {
-	Store   *dbstore.TemplateStore
-	Service *services.TemplateService
-	Handler *handlers.TemplateHandler
+type AppModule struct {
+	Store   *dbstore.AppStore     // Renamed from TemplateStore
+	Service *services.AppService  // Renamed from TemplateService
+	Handler *handlers.AppsHandler // Renamed from TemplateHandler
 }
 
 type DeploymentModule struct {
@@ -119,12 +118,12 @@ func NewApp(cnf *config.Config) (*App, error) {
 func (a *App) Run() error {
 
 	srv := &http.Server{
-		Addr:    fmt.Sprintf(":%s", strconv.Itoa(a.cnf.Server.Port)),
+		Addr:    fmt.Sprintf(":443"),
 		Handler: a.rtr,
 	}
 
 	go func() {
-		a.entry.WithField("address", srv.Addr).Info("Starting server on port " + strconv.Itoa(a.cnf.Server.Port))
+		a.entry.WithField("address", srv.Addr).Info("Starting server on port 443 ")
 		// Handle TLS if configured
 		if a.cnf.Server.Tls.Enabled {
 			a.entry.Info("Starting server with TLS...")

+ 2 - 2
config.sample.yml

@@ -1,14 +1,14 @@
 # Server configuration
 server:
   host: "0.0.0.0"  # Listen on all interfaces
-  port: 8080       # HTTP port to listen on
+  # port: 443       # HTTP port to listen on
   tls:             # TLS/HTTPS configuration
     enabled: false  # Set to true to enable HTTPS
     cert_file: "/path/to/cert.pem"
     key_file: "/path/to/key.pem"
 
 # Database configuration
-database:
+database: 
   host: "localhost"
   port: 5432
   username: "byop_user"

+ 24 - 12
dbmanager/memory.go

@@ -11,12 +11,12 @@ type MemoryDbManager struct {
 	// In-memory database storage
 
 	// User storage
-	users map[string]*models.User
+	users map[int64]*models.User
 
 	// Client storage
-	clients map[string]*models.Client
+	clients map[int64]*models.Client
 	// Deployment storage
-	deployments map[string]*models.Deployment
+	deployments map[int64]*models.Deployment
 	// Other entity storage
 	// ...
 }
@@ -24,9 +24,9 @@ type MemoryDbManager struct {
 // NewMemoryDbManager creates a new MemoryDbManager
 func NewMemoryDbManager() *MemoryDbManager {
 	return &MemoryDbManager{
-		users:       make(map[string]*models.User),
-		clients:     make(map[string]*models.Client),
-		deployments: make(map[string]*models.Deployment),
+		users:       make(map[int64]*models.User),
+		clients:     make(map[int64]*models.Client),
+		deployments: make(map[int64]*models.Deployment),
 	}
 }
 
@@ -78,21 +78,27 @@ func (m *MemoryDbManager) Create(entityType string, entity interface{}) error {
 
 // GetByID retrieves an entity by ID from the in-memory database
 func (m *MemoryDbManager) GetByID(entityType string, id string) (interface{}, error) {
+	// Convert string ID to int64 for the new ID format
+	var intID int64
+	if _, err := fmt.Sscanf(id, "%d", &intID); err != nil {
+		return nil, fmt.Errorf("invalid ID format: %s", id)
+	}
+
 	switch entityType {
 	case "users":
-		user, exists := m.users[id]
+		user, exists := m.users[intID]
 		if !exists {
 			return nil, fmt.Errorf("user not found")
 		}
 		return user, nil
 	case "clients":
-		client, exists := m.clients[id]
+		client, exists := m.clients[intID]
 		if !exists {
 			return nil, fmt.Errorf("client not found")
 		}
 		return client, nil
 	case "deployments":
-		deployment, exists := m.deployments[id]
+		deployment, exists := m.deployments[intID]
 		if !exists {
 			return nil, fmt.Errorf("deployment not found")
 		}
@@ -131,13 +137,19 @@ func (m *MemoryDbManager) Update(entityType string, entity interface{}) error {
 
 // Delete deletes an entity by ID from the in-memory database
 func (m *MemoryDbManager) Delete(entityType string, id string) error {
+	// Convert string ID to int64 for the new ID format
+	var intID int64
+	if _, err := fmt.Sscanf(id, "%d", &intID); err != nil {
+		return fmt.Errorf("invalid ID format: %s", id)
+	}
+
 	switch entityType {
 	case "users":
-		delete(m.users, id)
+		delete(m.users, intID)
 	case "clients":
-		delete(m.clients, id)
+		delete(m.clients, intID)
 	case "deployments":
-		delete(m.deployments, id)
+		delete(m.deployments, intID)
 	default:
 		return fmt.Errorf("unsupported entity type: %s", entityType)
 	}

+ 21 - 12
dbstore/app.go

@@ -5,11 +5,10 @@ import (
 
 	"git.linuxforward.com/byop/byop-engine/dbmanager"
 	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
 	"gorm.io/gorm"
 )
 
-// AppStore handles database operations for apps
+// AppStore handles database operations for deployment apps
 type AppStore struct {
 	db *gorm.DB
 }
@@ -23,19 +22,16 @@ func NewAppStore(dbManager dbmanager.DbManager) *AppStore {
 
 // Create creates a new app
 func (as *AppStore) Create(app *models.App) error {
-	// Generate ID if not provided
-	if app.ID == "" {
-		app.ID = uuid.New().String()
-	}
-
-	// GORM will handle created_at and updated_at automatically
+	// GORM will handle ID auto-increment and created_at/updated_at automatically
 	return as.db.Create(app).Error
 }
 
 // GetByID retrieves an app by ID
-func (as *AppStore) GetByID(id string) (*models.App, error) {
+func (as *AppStore) GetByID(id int64) (*models.App, error) {
 	var app models.App
-	result := as.db.First(&app, "id = ?", id)
+	result := as.db.
+		Where("rowid = ?", id). // Use SQLite's rowid explicitly
+		First(&app)
 	if result.Error != nil {
 		if result.Error == gorm.ErrRecordNotFound {
 			return nil, nil // No app found
@@ -51,7 +47,7 @@ func (as *AppStore) Update(app *models.App) error {
 }
 
 // Delete deletes an app by ID
-func (as *AppStore) Delete(id string) error {
+func (as *AppStore) Delete(id int64) error {
 	return as.db.Delete(&models.App{}, "id = ?", id).Error
 }
 
@@ -76,7 +72,7 @@ func (as *AppStore) List(filter map[string]interface{}) ([]*models.App, error) {
 }
 
 // GetAppWithDeployments retrieves an app by ID with associated deployments
-func (as *AppStore) GetAppWithDeployments(id string) (*models.App, error) {
+func (as *AppStore) GetAppWithDeployments(id int64) (*models.App, error) {
 	var app models.App
 	result := as.db.Preload("Deployments").First(&app, "id = ?", id)
 	if result.Error != nil {
@@ -87,3 +83,16 @@ func (as *AppStore) GetAppWithDeployments(id string) (*models.App, error) {
 	}
 	return &app, nil
 }
+
+// GetByVersion retrieves an app by name and version
+func (as *AppStore) GetByVersion(name string, version string) (*models.App, error) {
+	var app models.App
+	result := as.db.Where("name = ? AND version = ?", name, version).First(&app)
+	if result.Error != nil {
+		if result.Error == gorm.ErrRecordNotFound {
+			return nil, nil // No app found
+		}
+		return nil, fmt.Errorf("failed to get app: %w", result.Error)
+	}
+	return &app, nil
+}

+ 11 - 12
dbstore/client.go

@@ -5,7 +5,6 @@ import (
 
 	"git.linuxforward.com/byop/byop-engine/dbmanager"
 	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
 	"gorm.io/gorm"
 )
 
@@ -23,19 +22,16 @@ func NewClientStore(dbManager dbmanager.DbManager) *ClientStore {
 
 // Create creates a new client
 func (cs *ClientStore) Create(client *models.Client) error {
-	// Generate ID if not provided
-	if client.ID == "" {
-		client.ID = uuid.New().String()
-	}
-
-	// GORM will handle created_at and updated_at automatically
+	// GORM will handle ID auto-increment, created_at and updated_at automatically
 	return cs.db.Create(client).Error
 }
 
 // GetByID retrieves a client by ID
-func (cs *ClientStore) GetByID(id string) (*models.Client, error) {
+func (cs *ClientStore) GetByID(id int64) (*models.Client, error) {
 	var client models.Client
-	result := cs.db.First(&client, "id = ?", id)
+	result := cs.db.
+		Where("rowid = ?", id). // Use SQLite's rowid explicitly
+		First(&client)
 	if result.Error != nil {
 		if result.Error == gorm.ErrRecordNotFound {
 			return nil, nil // No client found
@@ -51,7 +47,7 @@ func (cs *ClientStore) Update(client *models.Client) error {
 }
 
 // Delete deletes a client by ID
-func (cs *ClientStore) Delete(id string) error {
+func (cs *ClientStore) Delete(id int64) error {
 	return cs.db.Delete(&models.Client{}, "id = ?", id).Error
 }
 
@@ -76,9 +72,12 @@ func (cs *ClientStore) List(filter map[string]interface{}) ([]*models.Client, er
 }
 
 // GetClientWithDeployments retrieves a client by ID with associated deployments
-func (cs *ClientStore) GetClientWithDeployments(id string) (*models.Client, error) {
+func (cs *ClientStore) GetClientWithDeployments(id int64) (*models.Client, error) {
 	var client models.Client
-	result := cs.db.Preload("Deployments").First(&client, "id = ?", id)
+	result := cs.db.Preload("Deployments").
+		Where("rowid = ?", id). // Use SQLite's rowid explicitly
+		First(&client)
+
 	if result.Error != nil {
 		if result.Error == gorm.ErrRecordNotFound {
 			return nil, nil // No client found

+ 86 - 0
dbstore/component.go

@@ -0,0 +1,86 @@
+package dbstore
+
+import (
+	"fmt"
+
+	"git.linuxforward.com/byop/byop-engine/dbmanager"
+	"git.linuxforward.com/byop/byop-engine/models"
+	"gorm.io/gorm"
+)
+
+// ComponentStore handles database operations for components
+type ComponentStore struct {
+	db *gorm.DB
+}
+
+// NewComponentStore creates a new ComponentStore
+func NewComponentStore(dbManager dbmanager.DbManager) *ComponentStore {
+	return &ComponentStore{
+		db: dbManager.GetDB(),
+	}
+}
+
+// Create creates a new component
+func (cs *ComponentStore) Create(component *models.Component) error {
+	// GORM will handle ID auto-increment and created_at/updated_at automatically
+	return cs.db.Create(component).Error
+}
+
+// GetByID retrieves a component by ID using SQLite rowid
+func (cs *ComponentStore) GetByID(id int64) (*models.Component, error) {
+	var component models.Component
+	// Use SQLite's rowid explicitly
+	result := cs.db.
+		Where("rowid = ?", id).
+		First(&component)
+	if result.Error != nil {
+		if result.Error == gorm.ErrRecordNotFound {
+			return nil, nil // No component found
+		}
+		return nil, fmt.Errorf("failed to get component: %w", result.Error)
+	}
+	return &component, nil
+}
+
+// Update updates an existing component
+func (cs *ComponentStore) Update(component *models.Component) error {
+	return cs.db.Save(component).Error
+}
+
+// Delete deletes a component by ID
+func (cs *ComponentStore) Delete(id int64) error {
+	return cs.db.Delete(&models.Component{}, "id = ?", id).Error
+}
+
+// List retrieves all components with optional filtering
+func (cs *ComponentStore) List(filter map[string]interface{}) ([]*models.Component, error) {
+	var components []*models.Component
+
+	// Build query from filters
+	query := cs.db
+	if filter != nil {
+		for key, value := range filter {
+			query = query.Where(key+" = ?", value)
+		}
+	}
+
+	// Execute query
+	if err := query.Find(&components).Error; err != nil {
+		return nil, fmt.Errorf("failed to list components: %w", err)
+	}
+
+	return components, nil
+}
+
+// GetComponentWithDeployments retrieves a component by ID with associated deployments
+func (cs *ComponentStore) GetComponentWithDeployments(id int64) (*models.Component, error) {
+	var component models.Component
+	result := cs.db.Preload("Deployments").First(&component, "id = ?", id)
+	if result.Error != nil {
+		if result.Error == gorm.ErrRecordNotFound {
+			return nil, nil // No component found
+		}
+		return nil, fmt.Errorf("failed to get component: %w", result.Error)
+	}
+	return &component, nil
+}

+ 23 - 28
dbstore/deployment.go

@@ -6,7 +6,6 @@ import (
 
 	"git.linuxforward.com/byop/byop-engine/dbmanager"
 	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
 	"gorm.io/gorm"
 )
 
@@ -24,11 +23,6 @@ func NewDeploymentStore(dbManager dbmanager.DbManager) *DeploymentStore {
 
 // Create creates a new deployment
 func (ds *DeploymentStore) Create(deployment *models.Deployment) error {
-	// Generate ID if not provided
-	if deployment.ID == "" {
-		deployment.ID = uuid.New().String()
-	}
-
 	// Ensure logs, metrics, and alerts config are properly JSON serialized
 	if err := ds.serializeConfigFields(deployment); err != nil {
 		return fmt.Errorf("failed to serialize config fields: %w", err)
@@ -45,12 +39,9 @@ func (ds *DeploymentStore) Create(deployment *models.Deployment) error {
 		for i := range deployment.DeployedApps {
 			app := &deployment.DeployedApps[i]
 
-			// Ensure each deployed app has an ID
-			if app.ID == "" {
-				app.ID = uuid.New().String()
-			}
-
-			// Set the deployment ID (ensure relationship is maintained)
+			// Ensure ID is 0 so GORM can auto-generate it
+			app.ID = 0
+			// GORM will auto-generate the ID, just set the deployment ID relationship
 			app.DeploymentID = deployment.ID
 
 			// Create the deployed app
@@ -61,7 +52,7 @@ func (ds *DeploymentStore) Create(deployment *models.Deployment) error {
 			// Handle resources if provided
 			if app.Resources != (models.ResourceAllocation{}) {
 				resource := models.DeployedAppResource{
-					ID:            uuid.New().String(),
+					ID:            0, // Ensure ID is 0 for auto-increment
 					DeployedAppID: app.ID,
 					CPU:           app.Resources.CPU,
 					CPUUsage:      app.Resources.CPUUsage,
@@ -82,13 +73,14 @@ func (ds *DeploymentStore) Create(deployment *models.Deployment) error {
 }
 
 // GetByID retrieves a deployment by ID
-func (ds *DeploymentStore) GetByID(id string) (*models.Deployment, error) {
+func (ds *DeploymentStore) GetByID(id int64) (*models.Deployment, error) {
 	var deployment models.Deployment
 
 	// Get deployment with all related deployed apps
 	err := ds.db.
 		Preload("DeployedApps").
-		First(&deployment, "id = ?", id).Error
+		Where("rowid = ?", id). // Use SQLite's rowid for ID
+		First(&deployment).Error
 
 	if err != nil {
 		if err == gorm.ErrRecordNotFound {
@@ -145,7 +137,7 @@ func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
 		}
 
 		// Create a map of existing app IDs for quick lookup
-		existingAppMap := make(map[string]bool)
+		existingAppMap := make(map[int64]bool)
 		for _, app := range existingApps {
 			existingAppMap[app.ID] = true
 		}
@@ -155,16 +147,14 @@ func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
 			app := &deployment.DeployedApps[i]
 
 			// If app has ID and exists, update it
-			if app.ID != "" && existingAppMap[app.ID] {
+			if app.ID != 0 && existingAppMap[app.ID] {
 				if err := tx.Save(app).Error; err != nil {
 					return err
 				}
 				delete(existingAppMap, app.ID)
 			} else {
-				// New app, create it
-				if app.ID == "" {
-					app.ID = uuid.New().String()
-				}
+				// New app, create it (GORM will auto-generate ID)
+				app.ID = 0 // Ensure ID is 0 for auto-increment
 				app.DeploymentID = deployment.ID
 				if err := tx.Create(app).Error; err != nil {
 					return err
@@ -180,9 +170,9 @@ func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
 				}
 
 				if result.Error == gorm.ErrRecordNotFound {
-					// Create new resource
+					// Create new resource (GORM will auto-generate ID)
 					resource = models.DeployedAppResource{
-						ID:            uuid.New().String(),
+						ID:            0, // Ensure ID is 0 for auto-increment
 						DeployedAppID: app.ID,
 						CPU:           app.Resources.CPU,
 						CPUUsage:      app.Resources.CPUUsage,
@@ -225,7 +215,7 @@ func (ds *DeploymentStore) Update(deployment *models.Deployment) error {
 }
 
 // Delete deletes a deployment by ID
-func (ds *DeploymentStore) Delete(id string) error {
+func (ds *DeploymentStore) Delete(id int64) error {
 	return ds.db.Transaction(func(tx *gorm.DB) error {
 		// Delete associated DeployedAppResources
 		var deployedApps []models.DeployedApp
@@ -297,7 +287,7 @@ func (ds *DeploymentStore) List(filter map[string]interface{}) ([]*models.Deploy
 }
 
 // GetByClientID retrieves deployments for a specific client
-func (ds *DeploymentStore) GetByClientID(clientID string) ([]*models.Deployment, error) {
+func (ds *DeploymentStore) GetByClientID(clientID int64) ([]*models.Deployment, error) {
 	return ds.List(map[string]interface{}{"client_id": clientID})
 }
 
@@ -306,9 +296,14 @@ func (ds *DeploymentStore) GetByUserID(userID string) ([]*models.Deployment, err
 	return ds.List(map[string]interface{}{"created_by": userID})
 }
 
-// GetByTemplateID retrieves deployments based on a specific template
-func (ds *DeploymentStore) GetByTemplateID(templateID string) ([]*models.Deployment, error) {
-	return ds.List(map[string]interface{}{"template_id": templateID})
+// GetByAppID retrieves deployments based on a specific app (was template)
+func (ds *DeploymentStore) GetByAppID(appID int64) ([]*models.Deployment, error) {
+	return ds.List(map[string]interface{}{"app_id": appID})
+}
+
+// GetByTemplateID is deprecated, use GetByAppID instead
+func (ds *DeploymentStore) GetByTemplateID(templateID int64) ([]*models.Deployment, error) {
+	return ds.GetByAppID(templateID)
 }
 
 // serializeConfigFields serializes JSON config fields to strings

+ 0 - 102
dbstore/template.go

@@ -1,102 +0,0 @@
-package dbstore
-
-import (
-	"fmt"
-
-	"git.linuxforward.com/byop/byop-engine/dbmanager"
-	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
-	"gorm.io/gorm"
-)
-
-// TemplateStore handles database operations for deployment templates
-type TemplateStore struct {
-	db *gorm.DB
-}
-
-// NewTemplateStore creates a new TemplateStore
-func NewTemplateStore(dbManager dbmanager.DbManager) *TemplateStore {
-	return &TemplateStore{
-		db: dbManager.GetDB(),
-	}
-}
-
-// Create creates a new template
-func (ts *TemplateStore) Create(template *models.Template) error {
-	// Generate ID if not provided
-	if template.ID == "" {
-		template.ID = uuid.New().String()
-	}
-
-	// GORM will handle created_at and updated_at automatically
-	return ts.db.Create(template).Error
-}
-
-// GetByID retrieves a template by ID
-func (ts *TemplateStore) GetByID(id string) (*models.Template, error) {
-	var template models.Template
-	result := ts.db.First(&template, "id = ?", id)
-	if result.Error != nil {
-		if result.Error == gorm.ErrRecordNotFound {
-			return nil, nil // No template found
-		}
-		return nil, fmt.Errorf("failed to get template: %w", result.Error)
-	}
-	return &template, nil
-}
-
-// Update updates an existing template
-func (ts *TemplateStore) Update(template *models.Template) error {
-	return ts.db.Save(template).Error
-}
-
-// Delete deletes a template by ID
-func (ts *TemplateStore) Delete(id string) error {
-	return ts.db.Delete(&models.Template{}, "id = ?", id).Error
-}
-
-// List retrieves all templates with optional filtering
-func (ts *TemplateStore) List(filter map[string]interface{}) ([]*models.Template, error) {
-	var templates []*models.Template
-
-	// Build query from filters
-	query := ts.db
-	if filter != nil {
-		for key, value := range filter {
-			query = query.Where(key+" = ?", value)
-		}
-	}
-
-	// Execute query
-	if err := query.Find(&templates).Error; err != nil {
-		return nil, fmt.Errorf("failed to list templates: %w", err)
-	}
-
-	return templates, nil
-}
-
-// GetTemplateWithDeployments retrieves a template by ID with associated deployments
-func (ts *TemplateStore) GetTemplateWithDeployments(id string) (*models.Template, error) {
-	var template models.Template
-	result := ts.db.Preload("Deployments").First(&template, "id = ?", id)
-	if result.Error != nil {
-		if result.Error == gorm.ErrRecordNotFound {
-			return nil, nil // No template found
-		}
-		return nil, fmt.Errorf("failed to get template: %w", result.Error)
-	}
-	return &template, nil
-}
-
-// GetByVersion retrieves a template by name and version
-func (ts *TemplateStore) GetByVersion(name string, version string) (*models.Template, error) {
-	var template models.Template
-	result := ts.db.Where("name = ? AND version = ?", name, version).First(&template)
-	if result.Error != nil {
-		if result.Error == gorm.ErrRecordNotFound {
-			return nil, nil // No template found
-		}
-		return nil, fmt.Errorf("failed to get template: %w", result.Error)
-	}
-	return &template, nil
-}

+ 7 - 11
dbstore/user.go

@@ -3,7 +3,6 @@ package dbstore
 import (
 	"git.linuxforward.com/byop/byop-engine/dbmanager"
 	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
 	"gorm.io/gorm"
 )
 
@@ -21,19 +20,16 @@ func NewUserStore(dbManager dbmanager.DbManager) *UserStore {
 
 // Create creates a new user
 func (us *UserStore) Create(user *models.User) error {
-	// Generate ID if not provided
-	if user.ID == "" {
-		user.ID = uuid.New().String()
-	}
-
-	// GORM will handle created_at and updated_at automatically
+	// GORM will handle ID auto-increment and created_at/updated_at automatically
 	return us.db.Create(user).Error
 }
 
 // GetByID retrieves a user by ID
-func (us *UserStore) GetByID(id string) (*models.User, error) {
+func (us *UserStore) GetByID(id int64) (*models.User, error) {
 	var user models.User
-	result := us.db.First(&user, "id = ?", id)
+	result := us.db.
+		Where("rowid = ?", id). // Use SQLite's rowid explicitly
+		First(&user)
 	if result.Error != nil {
 		if result.Error == gorm.ErrRecordNotFound {
 			return nil, nil // No user found
@@ -57,7 +53,7 @@ func (us *UserStore) GetByUsername(id string) (*models.User, error) {
 }
 
 // ListDeploymentsByUserID retrieves all deployments for a user by ID
-func (us *UserStore) ListDeploymentsByUserID(userID string) ([]*models.Deployment, error) {
+func (us *UserStore) ListDeploymentsByUserID(userID int64) ([]*models.Deployment, error) {
 	var deployments []*models.Deployment
 	result := us.db.Where("user_id = ?", userID).Find(&deployments)
 	if result.Error != nil {
@@ -72,7 +68,7 @@ func (us *UserStore) Update(user *models.User) error {
 }
 
 // Delete deletes a user by ID
-func (us *UserStore) Delete(id string) error {
+func (us *UserStore) Delete(id int64) error {
 	return us.db.Delete(&models.User{}, "id = ?", id).Error
 }
 

+ 73 - 38
handlers/apps.go

@@ -3,36 +3,27 @@ package handlers
 import (
 	"fmt"
 	"net/http"
+	"strconv"
 
 	"git.linuxforward.com/byop/byop-engine/models"
 	"git.linuxforward.com/byop/byop-engine/services"
 	"github.com/gin-gonic/gin"
 )
 
-// AppHandler handles application-related operations
-type AppHandler struct {
+// AppsHandler handles app-related operations
+type AppsHandler struct {
 	service *services.AppService
 }
 
-// NewAppHandler creates a new AppHandler
-func NewAppHandler(service *services.AppService) *AppHandler {
-	return &AppHandler{
+// NewAppsHandler creates a new AppsHandler
+func NewAppsHandler(service *services.AppService) *AppsHandler {
+	return &AppsHandler{
 		service: service,
 	}
 }
 
-// RegisterRoutes registers routes for app operations
-func (h *AppHandler) RegisterRoutes(r *gin.RouterGroup) {
-	r.GET("/", h.ListApps)
-	r.POST("/", h.CreateApp)
-	r.GET("/:id", h.GetApp)
-	r.PUT("/:id", h.UpdateApp)
-	r.DELETE("/:id", h.DeleteApp)
-	r.GET("/:id/deployments", h.GetAppDeployments)
-}
-
-// ListApps returns all applications with optional filtering
-func (h *AppHandler) ListApps(c *gin.Context) {
+// ListApps returns all apps with optional filtering
+func (h *AppsHandler) ListApps(c *gin.Context) {
 	filter := make(map[string]interface{})
 
 	// Attempt to bind query parameters, but allow empty filters
@@ -43,15 +34,15 @@ func (h *AppHandler) ListApps(c *gin.Context) {
 
 	apps, err := h.service.ListApps(filter)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to list applications: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to list apps: %v", err)})
 		return
 	}
 
 	c.JSON(http.StatusOK, apps)
 }
 
-// CreateApp creates a new application
-func (h *AppHandler) CreateApp(c *gin.Context) {
+// CreateApp creates a new deployment app
+func (h *AppsHandler) CreateApp(c *gin.Context) {
 	var app models.App
 
 	if err := c.ShouldBindJSON(&app); err != nil {
@@ -66,34 +57,44 @@ func (h *AppHandler) CreateApp(c *gin.Context) {
 	}
 
 	if err := h.service.CreateApp(&app); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to create application: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to create app: %v", err)})
 		return
 	}
 
 	c.JSON(http.StatusCreated, app)
 }
 
-// GetApp returns a specific application
-func (h *AppHandler) GetApp(c *gin.Context) {
-	id := c.Param("id")
+// GetApp returns a specific app
+func (h *AppsHandler) GetApp(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid app ID"})
+		return
+	}
 
 	app, err := h.service.GetApp(id)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch application: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch app: %v", err)})
 		return
 	}
 
 	if app == nil {
-		c.JSON(http.StatusNotFound, gin.H{"error": "Application not found"})
+		c.JSON(http.StatusNotFound, gin.H{"error": "App not found"})
 		return
 	}
 
 	c.JSON(http.StatusOK, app)
 }
 
-// UpdateApp updates an application
-func (h *AppHandler) UpdateApp(c *gin.Context) {
-	id := c.Param("id")
+// UpdateApp updates an app
+func (h *AppsHandler) UpdateApp(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid app ID"})
+		return
+	}
 
 	var updatedApp models.App
 	if err := c.ShouldBindJSON(&updatedApp); err != nil {
@@ -105,34 +106,68 @@ func (h *AppHandler) UpdateApp(c *gin.Context) {
 	updatedApp.ID = id
 
 	if err := h.service.UpdateApp(&updatedApp); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update application: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update app: %v", err)})
 		return
 	}
 
 	c.JSON(http.StatusOK, updatedApp)
 }
 
-// DeleteApp deletes an application
-func (h *AppHandler) DeleteApp(c *gin.Context) {
-	id := c.Param("id")
+// DeleteApp deletes an app
+func (h *AppsHandler) DeleteApp(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid app ID"})
+		return
+	}
 
 	if err := h.service.DeleteApp(id); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete application: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete app: %v", err)})
 		return
 	}
 
 	c.Status(http.StatusNoContent)
 }
 
-// GetAppDeployments returns all deployments for an application
-func (h *AppHandler) GetAppDeployments(c *gin.Context) {
-	id := c.Param("id")
+// GetAppDeployments returns all deployments for an app
+func (h *AppsHandler) GetAppDeployments(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid app ID"})
+		return
+	}
 
 	deployments, err := h.service.GetAppDeployments(id)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch application deployments: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch app deployments: %v", err)})
 		return
 	}
 
 	c.JSON(http.StatusOK, deployments)
 }
+
+// GetAppByVersion handles retrieval of an app by name and version
+func (h *AppsHandler) GetAppByVersion(c *gin.Context) {
+	name := c.Query("name")
+	version := c.Query("version")
+
+	if name == "" || version == "" {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Both name and version parameters are required"})
+		return
+	}
+
+	app, err := h.service.GetAppByVersion(name, version)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch app: %v", err)})
+		return
+	}
+
+	if app == nil {
+		c.JSON(http.StatusNotFound, gin.H{"error": "App not found"})
+		return
+	}
+
+	c.JSON(http.StatusOK, app)
+}

+ 25 - 4
handlers/clients.go

@@ -3,6 +3,7 @@ package handlers
 import (
 	"fmt"
 	"net/http"
+	"strconv"
 
 	"git.linuxforward.com/byop/byop-engine/models"
 	"git.linuxforward.com/byop/byop-engine/services"
@@ -59,7 +60,12 @@ func (h *ClientHandler) CreateClient(c *gin.Context) {
 
 // GetClient returns a specific client
 func (h *ClientHandler) GetClient(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client ID"})
+		return
+	}
 
 	client, err := h.service.GetClient(id)
 	if err != nil {
@@ -77,7 +83,12 @@ func (h *ClientHandler) GetClient(c *gin.Context) {
 
 // UpdateClient updates a client
 func (h *ClientHandler) UpdateClient(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client ID"})
+		return
+	}
 
 	// Parse updated client data
 	var updatedClient models.Client
@@ -99,7 +110,12 @@ func (h *ClientHandler) UpdateClient(c *gin.Context) {
 
 // DeleteClient deletes a client
 func (h *ClientHandler) DeleteClient(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client ID"})
+		return
+	}
 
 	if err := h.service.DeleteClient(id); err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete client: %v", err)})
@@ -111,7 +127,12 @@ func (h *ClientHandler) DeleteClient(c *gin.Context) {
 
 // GetClientDeployments returns all deployments for a client
 func (h *ClientHandler) GetClientDeployments(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client ID"})
+		return
+	}
 
 	// Check if client exists
 	client, err := h.service.GetClient(id)

+ 159 - 0
handlers/components.go

@@ -0,0 +1,159 @@
+package handlers
+
+import (
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"git.linuxforward.com/byop/byop-engine/models"
+	"git.linuxforward.com/byop/byop-engine/services"
+	"github.com/gin-gonic/gin"
+)
+
+// ComponentHandler handles component-related operations
+type ComponentHandler struct {
+	service *services.ComponentService
+}
+
+// NewComponentHandler creates a new ComponentHandler
+func NewComponentHandler(service *services.ComponentService) *ComponentHandler {
+	return &ComponentHandler{
+		service: service,
+	}
+}
+
+// RegisterRoutes registers routes for component operations
+func (h *ComponentHandler) RegisterRoutes(r *gin.RouterGroup) {
+	r.GET("/", h.ListComponents)
+	r.POST("/", h.CreateComponent)
+	r.GET("/:id", h.GetComponent)
+	r.PUT("/:id", h.UpdateComponent)
+	r.DELETE("/:id", h.DeleteComponent)
+	r.GET("/:id/deployments", h.GetComponentDeployments)
+}
+
+// ListComponents returns all components with optional filtering
+func (h *ComponentHandler) ListComponents(c *gin.Context) {
+	filter := make(map[string]interface{})
+
+	// Attempt to bind query parameters, but allow empty filters
+	if err := c.ShouldBindQuery(&filter); err != nil && len(filter) > 0 {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters"})
+		return
+	}
+
+	components, err := h.service.ListComponents(filter)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to list components: %v", err)})
+		return
+	}
+
+	c.JSON(http.StatusOK, components)
+}
+
+// CreateComponent creates a new component
+func (h *ComponentHandler) CreateComponent(c *gin.Context) {
+	var component models.Component
+
+	if err := c.ShouldBindJSON(&component); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request body: %v", err)})
+		return
+	}
+
+	// Get the user ID from the context (set by auth middleware)
+	userID, exists := c.Get("userID")
+	if exists {
+		component.CreatedBy = userID.(string)
+	}
+
+	if err := h.service.CreateComponent(&component); err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to create component: %v", err)})
+		return
+	}
+
+	c.JSON(http.StatusCreated, component)
+}
+
+// GetComponent returns a specific component
+func (h *ComponentHandler) GetComponent(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid component ID"})
+		return
+	}
+
+	component, err := h.service.GetComponent(id)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch component: %v", err)})
+		return
+	}
+
+	if component == nil {
+		c.JSON(http.StatusNotFound, gin.H{"error": "Component not found"})
+		return
+	}
+
+	c.JSON(http.StatusOK, component)
+}
+
+// UpdateComponent updates a component
+func (h *ComponentHandler) UpdateComponent(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid component ID"})
+		return
+	}
+
+	var updatedComponent models.Component
+	if err := c.ShouldBindJSON(&updatedComponent); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request body: %v", err)})
+		return
+	}
+
+	// Ensure the ID matches the URL parameter
+	updatedComponent.ID = id
+
+	if err := h.service.UpdateComponent(&updatedComponent); err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update component: %v", err)})
+		return
+	}
+
+	c.JSON(http.StatusOK, updatedComponent)
+}
+
+// DeleteComponent deletes a component
+func (h *ComponentHandler) DeleteComponent(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid component ID"})
+		return
+	}
+
+	if err := h.service.DeleteComponent(id); err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete component: %v", err)})
+		return
+	}
+
+	c.Status(http.StatusNoContent)
+}
+
+// GetComponentDeployments returns all deployments for a component
+func (h *ComponentHandler) GetComponentDeployments(c *gin.Context) {
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid component ID"})
+		return
+	}
+
+	deployments, err := h.service.GetComponentDeployments(id)
+	if err != nil {
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch component deployments: %v", err)})
+		return
+	}
+
+	c.JSON(http.StatusOK, deployments)
+}

+ 40 - 9
handlers/deployments.go

@@ -3,6 +3,7 @@ package handlers
 import (
 	"fmt"
 	"net/http"
+	"strconv"
 
 	"git.linuxforward.com/byop/byop-engine/models"
 	"git.linuxforward.com/byop/byop-engine/services"
@@ -78,7 +79,12 @@ func (h *DeploymentHandler) CreateDeployment(c *gin.Context) {
 
 // GetDeployment returns a specific deployment
 func (h *DeploymentHandler) GetDeployment(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deployment ID"})
+		return
+	}
 
 	deployment, err := h.service.GetDeployment(id)
 	if err != nil {
@@ -96,7 +102,12 @@ func (h *DeploymentHandler) GetDeployment(c *gin.Context) {
 
 // UpdateDeployment updates a deployment
 func (h *DeploymentHandler) UpdateDeployment(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deployment ID"})
+		return
+	}
 
 	var updatedDeployment models.Deployment
 	if err := c.ShouldBindJSON(&updatedDeployment); err != nil {
@@ -117,7 +128,12 @@ func (h *DeploymentHandler) UpdateDeployment(c *gin.Context) {
 
 // DeleteDeployment deletes a deployment
 func (h *DeploymentHandler) DeleteDeployment(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deployment ID"})
+		return
+	}
 
 	if err := h.service.DeleteDeployment(id); err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete deployment: %v", err)})
@@ -129,7 +145,12 @@ func (h *DeploymentHandler) DeleteDeployment(c *gin.Context) {
 
 // UpdateDeploymentStatus updates the status of a deployment
 func (h *DeploymentHandler) UpdateDeploymentStatus(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid deployment ID"})
+		return
+	}
 
 	var statusUpdate struct {
 		Status string `json:"status" binding:"required"`
@@ -150,7 +171,12 @@ func (h *DeploymentHandler) UpdateDeploymentStatus(c *gin.Context) {
 
 // GetDeploymentsByClient returns all deployments for a specific client
 func (h *DeploymentHandler) GetDeploymentsByClient(c *gin.Context) {
-	clientID := c.Param("clientId")
+	clientIDStr := c.Param("clientId")
+	clientID, err := strconv.ParseInt(clientIDStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid client ID"})
+		return
+	}
 
 	deployments, err := h.service.GetDeploymentsByClientID(clientID)
 	if err != nil {
@@ -161,13 +187,18 @@ func (h *DeploymentHandler) GetDeploymentsByClient(c *gin.Context) {
 	c.JSON(http.StatusOK, deployments)
 }
 
-// GetDeploymentsByTemplate returns all deployments for a specific template
+// GetDeploymentsByTemplate returns all deployments for a specific app (was template)
 func (h *DeploymentHandler) GetDeploymentsByTemplate(c *gin.Context) {
-	templateID := c.Param("templateId")
+	appIDStr := c.Param("templateId") // Note: keeping templateId param for backward compatibility
+	appID, err := strconv.ParseInt(appIDStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid app ID"})
+		return
+	}
 
-	deployments, err := h.service.GetDeploymentsByTemplateID(templateID)
+	deployments, err := h.service.GetDeploymentsByAppID(appID)
 	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch template deployments: %v", err)})
+		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch app deployments: %v", err)})
 		return
 	}
 

+ 0 - 152
handlers/templates.go

@@ -1,152 +0,0 @@
-package handlers
-
-import (
-	"fmt"
-	"net/http"
-
-	"git.linuxforward.com/byop/byop-engine/models"
-	"git.linuxforward.com/byop/byop-engine/services"
-	"github.com/gin-gonic/gin"
-)
-
-// TemplateHandler handles template-related operations
-type TemplateHandler struct {
-	service *services.TemplateService
-}
-
-// NewTemplateHandler creates a new TemplateHandler
-func NewTemplateHandler(service *services.TemplateService) *TemplateHandler {
-	return &TemplateHandler{
-		service: service,
-	}
-}
-
-// ListTemplates returns all templates with optional filtering
-func (h *TemplateHandler) ListTemplates(c *gin.Context) {
-	filter := make(map[string]interface{})
-
-	// Attempt to bind query parameters, but allow empty filters
-	if err := c.ShouldBindQuery(&filter); err != nil && len(filter) > 0 {
-		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameters"})
-		return
-	}
-
-	templates, err := h.service.ListTemplates(filter)
-	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to list templates: %v", err)})
-		return
-	}
-
-	c.JSON(http.StatusOK, templates)
-}
-
-// CreateTemplate creates a new deployment template
-func (h *TemplateHandler) CreateTemplate(c *gin.Context) {
-	var template models.Template
-
-	if err := c.ShouldBindJSON(&template); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request body: %v", err)})
-		return
-	}
-
-	// Get the user ID from the context (set by auth middleware)
-	userID, exists := c.Get("userID")
-	if exists {
-		template.CreatedBy = userID.(string)
-	}
-
-	if err := h.service.CreateTemplate(&template); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to create template: %v", err)})
-		return
-	}
-
-	c.JSON(http.StatusCreated, template)
-}
-
-// GetTemplate returns a specific template
-func (h *TemplateHandler) GetTemplate(c *gin.Context) {
-	id := c.Param("id")
-
-	template, err := h.service.GetTemplate(id)
-	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch template: %v", err)})
-		return
-	}
-
-	if template == nil {
-		c.JSON(http.StatusNotFound, gin.H{"error": "Template not found"})
-		return
-	}
-
-	c.JSON(http.StatusOK, template)
-}
-
-// UpdateTemplate updates a template
-func (h *TemplateHandler) UpdateTemplate(c *gin.Context) {
-	id := c.Param("id")
-
-	var updatedTemplate models.Template
-	if err := c.ShouldBindJSON(&updatedTemplate); err != nil {
-		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Invalid request body: %v", err)})
-		return
-	}
-
-	// Ensure the ID matches the URL parameter
-	updatedTemplate.ID = id
-
-	if err := h.service.UpdateTemplate(&updatedTemplate); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update template: %v", err)})
-		return
-	}
-
-	c.JSON(http.StatusOK, updatedTemplate)
-}
-
-// DeleteTemplate deletes a template
-func (h *TemplateHandler) DeleteTemplate(c *gin.Context) {
-	id := c.Param("id")
-
-	if err := h.service.DeleteTemplate(id); err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete template: %v", err)})
-		return
-	}
-
-	c.Status(http.StatusNoContent)
-}
-
-// GetTemplateDeployments returns all deployments for a template
-func (h *TemplateHandler) GetTemplateDeployments(c *gin.Context) {
-	id := c.Param("id")
-
-	deployments, err := h.service.GetTemplateDeployments(id)
-	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch template deployments: %v", err)})
-		return
-	}
-
-	c.JSON(http.StatusOK, deployments)
-}
-
-// GetTemplateByVersion handles retrieval of a template by name and version
-func (h *TemplateHandler) GetTemplateByVersion(c *gin.Context) {
-	name := c.Query("name")
-	version := c.Query("version")
-
-	if name == "" || version == "" {
-		c.JSON(http.StatusBadRequest, gin.H{"error": "Both name and version parameters are required"})
-		return
-	}
-
-	template, err := h.service.GetTemplateByVersion(name, version)
-	if err != nil {
-		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to fetch template: %v", err)})
-		return
-	}
-
-	if template == nil {
-		c.JSON(http.StatusNotFound, gin.H{"error": "Template not found"})
-		return
-	}
-
-	c.JSON(http.StatusOK, template)
-}

+ 13 - 2
handlers/tickets.go

@@ -3,6 +3,7 @@ package handlers
 import (
 	"fmt"
 	"net/http"
+	"strconv"
 
 	"git.linuxforward.com/byop/byop-engine/models"
 	"github.com/gin-gonic/gin"
@@ -53,7 +54,12 @@ func (h *TicketHandler) CreateTicket(c *gin.Context) {
 
 // GetTicket returns a specific ticket
 func (h *TicketHandler) GetTicket(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ticket ID"})
+		return
+	}
 
 	// TODO: Fetch ticket from database
 	ticket := models.Ticket{ID: id}
@@ -63,7 +69,12 @@ func (h *TicketHandler) GetTicket(c *gin.Context) {
 
 // UpdateTicket updates a ticket
 func (h *TicketHandler) UpdateTicket(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ticket ID"})
+		return
+	}
 
 	var ticket models.Ticket
 	if err := c.ShouldBindJSON(&ticket); err != nil {

+ 28 - 4
handlers/users.go

@@ -3,6 +3,7 @@ package handlers
 import (
 	"fmt"
 	"net/http"
+	"strconv"
 
 	"git.linuxforward.com/byop/byop-engine/models"
 	"git.linuxforward.com/byop/byop-engine/services"
@@ -40,7 +41,13 @@ func (h *UserHandler) CreateUser(c *gin.Context) {
 
 // GetUser retrieves a user by ID
 func (h *UserHandler) GetUser(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
+		return
+	}
+
 	user, err := h.service.GetUser(id)
 	if err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to get user: %v", err)})
@@ -57,7 +64,13 @@ func (h *UserHandler) GetUser(c *gin.Context) {
 
 // UpdateUser updates an existing user
 func (h *UserHandler) UpdateUser(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
+		return
+	}
+
 	var user *models.User
 
 	if err := c.ShouldBindJSON(&user); err != nil {
@@ -77,7 +90,13 @@ func (h *UserHandler) UpdateUser(c *gin.Context) {
 
 // DeleteUser deletes a user by ID
 func (h *UserHandler) DeleteUser(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
+		return
+	}
+
 	if err := h.service.DeleteUser(id); err != nil {
 		c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete user: %v", err)})
 		return
@@ -108,7 +127,12 @@ func (h *UserHandler) ListUsers(c *gin.Context) {
 
 // GetUserDeployments retrieves all deployments for a user
 func (h *UserHandler) GetUserDeployments(c *gin.Context) {
-	id := c.Param("id")
+	idStr := c.Param("id")
+	id, err := strconv.ParseInt(idStr, 10, 64)
+	if err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"})
+		return
+	}
 
 	deployments, err := h.service.GetUserDeployments(id)
 	if err != nil {

+ 51 - 57
models/apps.go

@@ -8,95 +8,89 @@ import (
 )
 
 type App struct {
-	ID          string `json:"id" gorm:"primaryKey"`
+	ID          int64  `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
 	Name        string `json:"name" gorm:"not null"`
 	Description string `json:"description"`
-	Type        string `json:"type" gorm:"index"` // frontend, backend, api, database, or microservice
-	Language    string `json:"language"`          // Programming language or framework
-	Version     string `json:"version"`           // Version number (e.g., 1.0.0)
+	Version     string `json:"version" gorm:"index"`
 
-	// Configuration details
-	ConfigFile   string `json:"configFile" gorm:"type:text"`   // JSON configuration as a string
-	EnvVariables string `json:"envVariables" gorm:"type:text"` // Environment variables as a string
+	// Configuration as JSON string in DB
+	ConfigJSON string `json:"-" gorm:"column:config;type:text"`
 
-	// Source code details
-	Repository   string `json:"repository"`                   // Git repository URL
-	Branch       string `json:"branch" gorm:"default:'main'"` // Git branch (default: main)
-	BuildCommand string `json:"buildCommand"`                 // Command to build the app
-
-	// Resource allocation - stored as JSON
-	ResourcesJSON     string `json:"-" gorm:"column:resources;type:text"`
-	ScaleSettingsJSON string `json:"-" gorm:"column:scale_settings;type:text"`
-
-	// Virtual fields for ORM serialization/deserialization
-	Resources     ResourceRequirements `json:"resources" gorm:"-"`
-	ScaleSettings ScaleSettings        `json:"scaleSettings" gorm:"-"`
+	// Virtual field for ORM serialization/deserialization
+	Config AppConfig `json:"config" gorm:"-"`
 
 	CreatedAt time.Time      `json:"createdAt" gorm:"autoCreateTime"`
 	UpdatedAt time.Time      `json:"updatedAt" gorm:"autoUpdateTime"`
-	CreatedBy string         `json:"createdBy" gorm:"index"` // User ID who created the app
+	CreatedBy string         `json:"createdBy" gorm:"index"` // User ID who created the template
 	DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`         // Soft delete support
 
 	// Relationships
-	Deployments []DeployedApp `json:"deployments" gorm:"foreignKey:AppID"` // Apps deployed in deployments
+	Deployments []Deployment `json:"deployments" gorm:"foreignKey:AppID"` // Deployments using this app
 }
 
-type ResourceRequirements struct {
+type AppConfig struct {
+	Components      []ComponentConfig `json:"components"`             // Components included in this app (renamed from apps)
+	NetworkPolicies []NetworkPolicy   `json:"networkPolicies"`        // Network policies to apply
+	EnvVariables    map[string]string `json:"envVariables,omitempty"` // Environment variables
+	Secrets         []SecretConfig    `json:"secrets,omitempty"`      // Secret configurations
+}
+
+type ComponentConfig struct {
+	ID           int64             `json:"id"`                     // Reference to the component
+	Name         string            `json:"name"`                   // Name of the component in this app
+	ExposedPorts []int             `json:"exposedPorts,omitempty"` // Ports to expose
+	PublicAccess bool              `json:"publicAccess"`           // Whether the component is publicly accessible
+	Resources    ResourceConfig    `json:"resources"`              // Resource allocation
+	Autoscaling  AutoscalingConfig `json:"autoscaling,omitempty"`  // Autoscaling configuration
+	EnvOverrides map[string]string `json:"envOverrides,omitempty"` // Environment variable overrides
+	ServiceMesh  bool              `json:"serviceMesh"`            // Whether to include in service mesh
+}
+
+type ResourceConfig struct {
 	CPU     string `json:"cpu"`     // e.g., "0.5"
 	Memory  string `json:"memory"`  // e.g., "512Mi"
 	Storage string `json:"storage"` // e.g., "1Gi"
 }
 
-type ScaleSettings struct {
-	MinInstances int `json:"minInstances"` // Minimum number of instances
-	MaxInstances int `json:"maxInstances"` // Maximum number of instances
-	CPUThreshold int `json:"cpuThreshold"` // CPU threshold for scaling (percentage)
+type AutoscalingConfig struct {
+	Enabled      bool   `json:"enabled"`      // Whether autoscaling is enabled
+	MinReplicas  int    `json:"minReplicas"`  // Minimum number of replicas
+	MaxReplicas  int    `json:"maxReplicas"`  // Maximum number of replicas
+	CPUThreshold int    `json:"cpuThreshold"` // CPU threshold for scaling (percentage)
+	Metric       string `json:"metric"`       // Metric to base scaling on (e.g., "cpu", "memory")
 }
 
-// AppType represents the type of application
-type AppType string
+type NetworkPolicy struct {
+	Name           string   `json:"name"`            // Policy name
+	FromComponents []string `json:"fromComponents"`  // Source components (renamed from FromApps)
+	ToComponents   []string `json:"toComponents"`    // Destination components (renamed from ToApps)
+	Ports          []int    `json:"ports,omitempty"` // Allowed ports
+	AllowEgress    bool     `json:"allowEgress"`     // Whether to allow egress traffic
+}
 
-const (
-	Frontend     AppType = "frontend"
-	Backend      AppType = "backend"
-	API          AppType = "api"
-	Database     AppType = "database"
-	Microservice AppType = "microservice"
-)
+type SecretConfig struct {
+	Name        string `json:"name"`        // Secret name
+	Description string `json:"description"` // Secret description
+	Required    bool   `json:"required"`    // Whether the secret is required
+}
 
 // BeforeSave serializes the embedded JSON fields
 func (a *App) BeforeSave(tx *gorm.DB) error {
-	var err error
-
-	// Marshal Resources to JSON
-	resourcesJSON, err := json.Marshal(a.Resources)
-	if err != nil {
-		return err
-	}
-	a.ResourcesJSON = string(resourcesJSON)
-
-	// Marshal ScaleSettings to JSON
-	scaleSettingsJSON, err := json.Marshal(a.ScaleSettings)
+	// Marshal Config to JSON
+	configJSON, err := json.Marshal(a.Config)
 	if err != nil {
 		return err
 	}
-	a.ScaleSettingsJSON = string(scaleSettingsJSON)
+	a.ConfigJSON = string(configJSON)
 
 	return nil
 }
 
 // AfterFind deserializes the JSON fields
 func (a *App) AfterFind(tx *gorm.DB) error {
-	// Unmarshal Resources from JSON
-	if a.ResourcesJSON != "" {
-		if err := json.Unmarshal([]byte(a.ResourcesJSON), &a.Resources); err != nil {
-			return err
-		}
-	}
-
-	// Unmarshal ScaleSettings from JSON
-	if a.ScaleSettingsJSON != "" {
-		if err := json.Unmarshal([]byte(a.ScaleSettingsJSON), &a.ScaleSettings); err != nil {
+	// Unmarshal Config from JSON
+	if a.ConfigJSON != "" {
+		if err := json.Unmarshal([]byte(a.ConfigJSON), &a.Config); err != nil {
 			return err
 		}
 	}

+ 9 - 9
models/client.go

@@ -7,15 +7,15 @@ import (
 )
 
 type Client struct {
-	ID           string         `json:"id" gorm:"primaryKey"`            // Unique identifier
-	Name         string         `json:"name" gorm:"not null"`            // Client name
-	ContactEmail string         `json:"contactEmail" gorm:"index"`       // Client contact email
-	ContactPhone string         `json:"contactPhone,omitempty"`          // Optional contact phone
-	Organization string         `json:"organization"`                    // Client organization name
-	Plan         PlanType       `json:"plan" gorm:"default:'basic'"`     // Client plan type (basic, pro, enterprise)
-	CreatedAt    time.Time      `json:"createdAt" gorm:"autoCreateTime"` // Creation timestamp
-	UpdatedAt    time.Time      `json:"updatedAt" gorm:"autoUpdateTime"` // Last update timestamp
-	DeletedAt    gorm.DeletedAt `json:"deletedAt" gorm:"index"`          // Soft delete support
+	ID           int64          `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
+	Name         string         `json:"name" gorm:"not null"`                            // Client name
+	ContactEmail string         `json:"contactEmail" gorm:"index"`                       // Client contact email
+	ContactPhone string         `json:"contactPhone,omitempty"`                          // Optional contact phone
+	Organization string         `json:"organization"`                                    // Client organization name
+	Plan         PlanType       `json:"plan" gorm:"default:'basic'"`                     // Client plan type (basic, pro, enterprise)
+	CreatedAt    time.Time      `json:"createdAt" gorm:"autoCreateTime"`                 // Creation timestamp
+	UpdatedAt    time.Time      `json:"updatedAt" gorm:"autoUpdateTime"`                 // Last update timestamp
+	DeletedAt    gorm.DeletedAt `json:"deletedAt" gorm:"index"`                          // Soft delete support
 
 	// GORM relationships
 	Deployments []Deployment `json:"deployments" gorm:"foreignKey:ClientID"` // Deployments belonging to this client

+ 105 - 0
models/components.go

@@ -0,0 +1,105 @@
+package models
+
+import (
+	"encoding/json"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+type Component struct {
+	ID          int64         `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
+	Name        string        `json:"name" gorm:"not null"`
+	Description string        `json:"description"`
+	Type        ComponentType `json:"type" gorm:"index"` // frontend, backend, api, database, or microservice
+	Language    string        `json:"language"`          // Programming language or framework
+	Version     string        `json:"version"`           // Version number (e.g., 1.0.0)
+
+	// Configuration details
+	ConfigFile   string `json:"configFile" gorm:"type:text"`   // JSON configuration as a string
+	EnvVariables string `json:"envVariables" gorm:"type:text"` // Environment variables as a string
+
+	// Source code details
+	Repository   string `json:"repository"`                   // Git repository URL
+	Branch       string `json:"branch" gorm:"default:'main'"` // Git branch (default: main)
+	BuildCommand string `json:"buildCommand"`                 // Command to build the app
+
+	// Resource allocation - stored as JSON
+	ResourcesJSON     string `json:"-" gorm:"column:resources;type:text"`
+	ScaleSettingsJSON string `json:"-" gorm:"column:scale_settings;type:text"`
+
+	// Virtual fields for ORM serialization/deserialization
+	Resources     ResourceRequirements `json:"resources" gorm:"-"`
+	ScaleSettings ScaleSettings        `json:"scaleSettings" gorm:"-"`
+
+	CreatedAt time.Time      `json:"createdAt" gorm:"autoCreateTime"`
+	UpdatedAt time.Time      `json:"updatedAt" gorm:"autoUpdateTime"`
+	CreatedBy string         `json:"createdBy" gorm:"index"` // User ID who created the app
+	DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`         // Soft delete support
+
+	// Relationships
+	Deployments []DeployedApp `json:"deployments" gorm:"foreignKey:ComponentID"` // Apps deployed in deployments
+}
+
+type ResourceRequirements struct {
+	CPU     string `json:"cpu"`     // e.g., "0.5"
+	Memory  string `json:"memory"`  // e.g., "512Mi"
+	Storage string `json:"storage"` // e.g., "1Gi"
+}
+
+type ScaleSettings struct {
+	MinInstances int `json:"minInstances"` // Minimum number of instances
+	MaxInstances int `json:"maxInstances"` // Maximum number of instances
+	CPUThreshold int `json:"cpuThreshold"` // CPU threshold for scaling (percentage)
+}
+
+// ComponentType represents the type of component
+type ComponentType string
+
+const (
+	Frontend     ComponentType = "frontend"
+	Backend      ComponentType = "backend"
+	API          ComponentType = "api"
+	Database     ComponentType = "database"
+	Microservice ComponentType = "microservice"
+)
+
+// BeforeSave serializes the embedded JSON fields
+func (c *Component) BeforeSave(tx *gorm.DB) error {
+	var err error
+
+	// Marshal Resources to JSON
+	resourcesJSON, err := json.Marshal(c.Resources)
+	if err != nil {
+		return err
+	}
+	c.ResourcesJSON = string(resourcesJSON)
+
+	// Marshal ScaleSettings to JSON
+	scaleSettingsJSON, err := json.Marshal(c.ScaleSettings)
+	if err != nil {
+		return err
+	}
+	c.ScaleSettingsJSON = string(scaleSettingsJSON)
+
+	return nil
+}
+
+// AfterFind deserializes the JSON fields
+func (c *Component) AfterFind(tx *gorm.DB) error {
+	// Unmarshal Resources from JSON
+	if c.ResourcesJSON != "" {
+		if err := json.Unmarshal([]byte(c.ResourcesJSON), &c.Resources); err != nil {
+			return err
+		}
+	}
+
+	// Unmarshal ScaleSettings from JSON
+	if c.ScaleSettingsJSON != "" {
+		if err := json.Unmarshal([]byte(c.ScaleSettingsJSON), &c.ScaleSettings); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}

+ 26 - 26
models/deployment.go

@@ -8,13 +8,13 @@ import (
 
 // Deployment represents a deployed instance of an application
 type Deployment struct {
-	ID          string `json:"id" gorm:"primaryKey"` // Unique identifier
-	Name        string `json:"name" gorm:"not null"` // Deployment name
-	Description string `json:"description"`          // Deployment description
+	ID          int64  `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
+	Name        string `json:"name" gorm:"not null"`                            // Deployment name
+	Description string `json:"description"`                                     // Deployment description
 
 	// Core relationships
-	TemplateID string `json:"templateId" gorm:"index"` // Reference to the template being deployed
-	ClientID   string `json:"clientId" gorm:"index"`   // Client this deployment belongs to
+	AppID    int64 `json:"appId" gorm:"index"`    // Reference to the app being deployed (was TemplateID)
+	ClientID int64 `json:"clientId" gorm:"index"` // Client this deployment belongs to
 
 	// Status and environment
 	Status      string `json:"status" gorm:"default:'pending'"`          // Current deployment status
@@ -42,18 +42,18 @@ type Deployment struct {
 
 // DeployedApp represents a specific app within a deployment
 type DeployedApp struct {
-	ID             string         `json:"id" gorm:"primaryKey"`                  // Unique identifier
-	DeploymentID   string         `json:"deploymentId" gorm:"index"`             // Reference to the parent deployment
-	AppID          string         `json:"appId" gorm:"index"`                    // Reference to the app being deployed
-	Status         string         `json:"status" gorm:"default:'pending'"`       // Status of this specific app's deployment
-	Version        string         `json:"version"`                               // Deployed version
-	URL            string         `json:"url"`                                   // URL to access this app
-	PodCount       int            `json:"podCount" gorm:"default:1"`             // Number of running instances/pods
-	HealthStatus   string         `json:"healthStatus" gorm:"default:'pending'"` // Current health status
-	ConfigSnapshot string         `json:"configSnapshot" gorm:"type:text"`       // Snapshot of configuration at deployment time
-	CreatedAt      time.Time      `json:"createdAt" gorm:"autoCreateTime"`       // Creation timestamp
-	UpdatedAt      time.Time      `json:"updatedAt" gorm:"autoUpdateTime"`       // Last update timestamp
-	DeletedAt      gorm.DeletedAt `json:"-" gorm:"index"`                        // Soft delete support
+	ID             int64          `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
+	DeploymentID   int64          `json:"deploymentId" gorm:"index"`                       // Reference to the parent deployment
+	ComponentID    int64          `json:"componentId" gorm:"index"`                        // Reference to the component being deployed (was AppID)
+	Status         string         `json:"status" gorm:"default:'pending'"`                 // Status of this specific app's deployment
+	Version        string         `json:"version"`                                         // Deployed version
+	URL            string         `json:"url"`                                             // URL to access this app
+	PodCount       int            `json:"podCount" gorm:"default:1"`                       // Number of running instances/pods
+	HealthStatus   string         `json:"healthStatus" gorm:"default:'pending'"`           // Current health status
+	ConfigSnapshot string         `json:"configSnapshot" gorm:"type:text"`                 // Snapshot of configuration at deployment time
+	CreatedAt      time.Time      `json:"createdAt" gorm:"autoCreateTime"`                 // Creation timestamp
+	UpdatedAt      time.Time      `json:"updatedAt" gorm:"autoUpdateTime"`                 // Last update timestamp
+	DeletedAt      gorm.DeletedAt `json:"-" gorm:"index"`                                  // Soft delete support
 
 	// GORM relationships - these will be serialized/deserialized as JSON
 	Resources ResourceAllocation `json:"resources" gorm:"-"` // Actual resources allocated
@@ -61,15 +61,15 @@ type DeployedApp struct {
 
 // App resource allocation (will be stored in DeployedAppResource table)
 type DeployedAppResource struct {
-	ID            string    `json:"id" gorm:"primaryKey"`              // Unique identifier
-	DeployedAppID string    `json:"deployedAppId" gorm:"uniqueIndex"`  // Reference to deployed app
-	CPU           string    `json:"cpu"`                               // Allocated CPU
-	CPUUsage      float64   `json:"cpuUsage"`                          // Current CPU usage percentage
-	Memory        string    `json:"memory"`                            // Allocated memory
-	MemoryUsage   float64   `json:"memoryUsage"`                       // Current memory usage percentage
-	Storage       string    `json:"storage"`                           // Allocated storage
-	StorageUsage  float64   `json:"storageUsage"`                      // Current storage usage percentage
-	LastUpdated   time.Time `json:"lastUpdated" gorm:"autoUpdateTime"` // When metrics were last updated
+	ID            int64     `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
+	DeployedAppID int64     `json:"deployedAppId" gorm:"uniqueIndex"`                // Reference to deployed app
+	CPU           string    `json:"cpu"`                                             // Allocated CPU
+	CPUUsage      float64   `json:"cpuUsage"`                                        // Current CPU usage percentage
+	Memory        string    `json:"memory"`                                          // Allocated memory
+	MemoryUsage   float64   `json:"memoryUsage"`                                     // Current memory usage percentage
+	Storage       string    `json:"storage"`                                         // Allocated storage
+	StorageUsage  float64   `json:"storageUsage"`                                    // Current storage usage percentage
+	LastUpdated   time.Time `json:"lastUpdated" gorm:"autoUpdateTime"`               // When metrics were last updated
 }
 
 // For backward compatibility

+ 0 - 99
models/template.go

@@ -1,99 +0,0 @@
-package models
-
-import (
-	"encoding/json"
-	"time"
-
-	"gorm.io/gorm"
-)
-
-type Template struct {
-	ID          string `json:"id" gorm:"primaryKey"`
-	Name        string `json:"name" gorm:"not null"`
-	Description string `json:"description"`
-	Version     string `json:"version" gorm:"index"`
-
-	// Configuration as JSON string in DB
-	ConfigJSON string `json:"-" gorm:"column:config;type:text"`
-
-	// Virtual field for ORM serialization/deserialization
-	Config TemplateConfig `json:"config" gorm:"-"`
-
-	CreatedAt time.Time      `json:"createdAt" gorm:"autoCreateTime"`
-	UpdatedAt time.Time      `json:"updatedAt" gorm:"autoUpdateTime"`
-	CreatedBy string         `json:"createdBy" gorm:"index"` // User ID who created the template
-	DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`         // Soft delete support
-
-	// Relationships
-	Deployments []Deployment `json:"deployments" gorm:"foreignKey:TemplateID"` // Deployments using this template
-}
-
-type TemplateConfig struct {
-	Apps            []AppConfig       `json:"apps"`                   // Apps included in this template
-	NetworkPolicies []NetworkPolicy   `json:"networkPolicies"`        // Network policies to apply
-	EnvVariables    map[string]string `json:"envVariables,omitempty"` // Environment variables
-	Secrets         []SecretConfig    `json:"secrets,omitempty"`      // Secret configurations
-}
-
-type AppConfig struct {
-	ID           string            `json:"id"`                     // Reference to the app
-	Name         string            `json:"name"`                   // Name of the app in this template
-	ExposedPorts []int             `json:"exposedPorts,omitempty"` // Ports to expose
-	PublicAccess bool              `json:"publicAccess"`           // Whether the app is publicly accessible
-	Resources    ResourceConfig    `json:"resources"`              // Resource allocation
-	Autoscaling  AutoscalingConfig `json:"autoscaling,omitempty"`  // Autoscaling configuration
-	EnvOverrides map[string]string `json:"envOverrides,omitempty"` // Environment variable overrides
-	ServiceMesh  bool              `json:"serviceMesh"`            // Whether to include in service mesh
-}
-
-type ResourceConfig struct {
-	CPU     string `json:"cpu"`     // e.g., "0.5"
-	Memory  string `json:"memory"`  // e.g., "512Mi"
-	Storage string `json:"storage"` // e.g., "1Gi"
-}
-
-type AutoscalingConfig struct {
-	Enabled      bool   `json:"enabled"`      // Whether autoscaling is enabled
-	MinReplicas  int    `json:"minReplicas"`  // Minimum number of replicas
-	MaxReplicas  int    `json:"maxReplicas"`  // Maximum number of replicas
-	CPUThreshold int    `json:"cpuThreshold"` // CPU threshold for scaling (percentage)
-	Metric       string `json:"metric"`       // Metric to base scaling on (e.g., "cpu", "memory")
-}
-
-type NetworkPolicy struct {
-	Name        string   `json:"name"`            // Policy name
-	FromApps    []string `json:"fromApps"`        // Source apps
-	ToApps      []string `json:"toApps"`          // Destination apps
-	Ports       []int    `json:"ports,omitempty"` // Allowed ports
-	AllowEgress bool     `json:"allowEgress"`     // Whether to allow egress traffic
-}
-
-type SecretConfig struct {
-	Name        string `json:"name"`        // Secret name
-	Description string `json:"description"` // Secret description
-	Required    bool   `json:"required"`    // Whether the secret is required
-}
-
-// BeforeSave serializes the embedded JSON fields
-func (t *Template) BeforeSave(tx *gorm.DB) error {
-	// Marshal Config to JSON
-	configJSON, err := json.Marshal(t.Config)
-	if err != nil {
-		return err
-	}
-	t.ConfigJSON = string(configJSON)
-
-	return nil
-}
-
-// AfterFind deserializes the JSON fields
-func (t *Template) AfterFind(tx *gorm.DB) error {
-	// Unmarshal Config from JSON
-	if t.ConfigJSON != "" {
-		if err := json.Unmarshal([]byte(t.ConfigJSON), &t.Config); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}

+ 1 - 1
models/ticket.go

@@ -5,7 +5,7 @@ import (
 )
 
 type Ticket struct {
-	ID        string    `json:"id"`
+	ID int64 `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
 	// TODO: Add Ticket fields
 	CreatedAt time.Time `json:"created_at"`
 	UpdatedAt time.Time `json:"updated_at"`

+ 10 - 10
models/user.go

@@ -11,16 +11,16 @@ import (
 
 // User represents a user in the system
 type User struct {
-	ID              string          `json:"id" gorm:"primaryKey"`                  // Unique identifier
-	Username        string          `json:"username" gorm:"uniqueIndex;not null"`  // Username
-	Email           string          `json:"email" gorm:"uniqueIndex;not null"`     // User's email address
-	Password        string          `json:"password,omitempty" gorm:"not null"`    // Password (hashed)
-	Role            string          `json:"role" gorm:"default:'user'"`            // User role
-	PreferencesJSON string          `json:"-" gorm:"column:preferences;type:text"` // User preferences stored as JSON string
-	Preferences     UserPreferences `json:"preferences" gorm:"-"`                  // User preferences (transient field)
-	CreatedAt       time.Time       `json:"createdAt" gorm:"autoCreateTime"`       // Creation timestamp
-	UpdatedAt       time.Time       `json:"updatedAt" gorm:"autoUpdateTime"`       // Last update timestamp
-	DeletedAt       gorm.DeletedAt  `json:"-" gorm:"index"`                        // Soft delete support
+	ID              int64           `gorm:"column:rowid;primaryKey;autoIncrement" json:"id"` // Unique identifier
+	Username        string          `json:"username" gorm:"uniqueIndex;not null"`            // Username
+	Email           string          `json:"email" gorm:"uniqueIndex;not null"`               // User's email address
+	Password        string          `json:"password,omitempty" gorm:"not null"`              // Password (hashed)
+	Role            string          `json:"role" gorm:"default:'user'"`                      // User role
+	PreferencesJSON string          `json:"-" gorm:"column:preferences;type:text"`           // User preferences stored as JSON string
+	Preferences     UserPreferences `json:"preferences" gorm:"-"`                            // User preferences (transient field)
+	CreatedAt       time.Time       `json:"createdAt" gorm:"autoCreateTime"`                 // Creation timestamp
+	UpdatedAt       time.Time       `json:"updatedAt" gorm:"autoUpdateTime"`                 // Last update timestamp
+	DeletedAt       gorm.DeletedAt  `json:"-" gorm:"index"`                                  // Soft delete support
 }
 
 // UserPreferences represents user-specific settings

+ 69 - 40
services/apps.go

@@ -5,10 +5,9 @@ import (
 
 	"git.linuxforward.com/byop/byop-engine/dbstore"
 	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
 )
 
-// AppService handles business logic for applications
+// AppService handles business logic for apps
 type AppService struct {
 	store *dbstore.AppStore
 }
@@ -18,41 +17,19 @@ func NewAppService(store *dbstore.AppStore) *AppService {
 	return &AppService{store: store}
 }
 
-// CreateApp creates a new application
+// CreateApp creates a new deployment app
 func (s *AppService) CreateApp(app *models.App) error {
-	// Generate UUID if not provided
-	if app.ID == "" {
-		app.ID = uuid.New().String()
-	}
-
-	// Set default resource values if not provided
-	if app.Resources.CPU == "" {
-		app.Resources.CPU = "0.5"
-	}
-	if app.Resources.Memory == "" {
-		app.Resources.Memory = "512Mi"
-	}
-	if app.Resources.Storage == "" {
-		app.Resources.Storage = "1Gi"
-	}
-
-	// Set default scale settings if not provided
-	if app.ScaleSettings.MinInstances == 0 {
-		app.ScaleSettings.MinInstances = 1
-	}
-	if app.ScaleSettings.MaxInstances == 0 {
-		app.ScaleSettings.MaxInstances = 3
-	}
-	if app.ScaleSettings.CPUThreshold == 0 {
-		app.ScaleSettings.CPUThreshold = 80
+	// Validate app configuration
+	if err := validateAppConfig(app.Config); err != nil {
+		return fmt.Errorf("invalid app configuration: %w", err)
 	}
 
 	// Persist the app
 	return s.store.Create(app)
 }
 
-// GetApp retrieves an application by ID
-func (s *AppService) GetApp(id string) (*models.App, error) {
+// GetApp retrieves an app by ID
+func (s *AppService) GetApp(id int64) (*models.App, error) {
 	app, err := s.store.GetByID(id)
 	if err != nil {
 		return nil, fmt.Errorf("failed to retrieve app: %w", err)
@@ -60,9 +37,9 @@ func (s *AppService) GetApp(id string) (*models.App, error) {
 	return app, nil
 }
 
-// UpdateApp updates an existing application
+// UpdateApp updates an existing app
 func (s *AppService) UpdateApp(app *models.App) error {
-	if app.ID == "" {
+	if app.ID == 0 {
 		return fmt.Errorf("app ID is required for update")
 	}
 
@@ -72,40 +49,56 @@ func (s *AppService) UpdateApp(app *models.App) error {
 		return fmt.Errorf("failed to check if app exists: %w", err)
 	}
 	if existingApp == nil {
-		return fmt.Errorf("app with ID %s not found", app.ID)
+		return fmt.Errorf("app with ID %d not found", app.ID)
+	}
+
+	// Validate app configuration
+	if err := validateAppConfig(app.Config); err != nil {
+		return fmt.Errorf("invalid app configuration: %w", err)
 	}
 
 	return s.store.Update(app)
 }
 
-// DeleteApp deletes an application by ID
-func (s *AppService) DeleteApp(id string) error {
+// DeleteApp deletes an app by ID
+func (s *AppService) DeleteApp(id int64) error {
 	// Check if app exists
 	app, err := s.store.GetByID(id)
 	if err != nil {
 		return fmt.Errorf("failed to check if app exists: %w", err)
 	}
 	if app == nil {
-		return fmt.Errorf("app with ID %s not found", id)
+		return fmt.Errorf("app with ID %d not found", id)
+	}
+
+	// Check if the app has deployments
+	appWithDeployments, err := s.store.GetAppWithDeployments(id)
+	if err != nil {
+		return fmt.Errorf("failed to check app deployments: %w", err)
+	}
+
+	// Don't allow deletion if there are active deployments
+	if len(appWithDeployments.Deployments) > 0 {
+		return fmt.Errorf("cannot delete app with active deployments")
 	}
 
 	return s.store.Delete(id)
 }
 
-// ListApps retrieves all applications with optional filtering
+// ListApps retrieves all apps with optional filtering
 func (s *AppService) ListApps(filter map[string]interface{}) ([]*models.App, error) {
 	return s.store.List(filter)
 }
 
-// GetAppDeployments retrieves all deployments for an application
-func (s *AppService) GetAppDeployments(id string) ([]models.DeployedApp, error) {
+// GetAppDeployments retrieves all deployments for an app
+func (s *AppService) GetAppDeployments(id int64) ([]models.Deployment, error) {
 	// First check if the app exists
 	app, err := s.store.GetByID(id)
 	if err != nil {
 		return nil, fmt.Errorf("failed to check if app exists: %w", err)
 	}
 	if app == nil {
-		return nil, fmt.Errorf("app with ID %s not found", id)
+		return nil, fmt.Errorf("app with ID %d not found", id)
 	}
 
 	// Get app with deployments
@@ -116,3 +109,39 @@ func (s *AppService) GetAppDeployments(id string) ([]models.DeployedApp, error)
 
 	return appWithDeployments.Deployments, nil
 }
+
+// GetAppByVersion retrieves an app by name and version
+func (s *AppService) GetAppByVersion(name, version string) (*models.App, error) {
+	app, err := s.store.GetByVersion(name, version)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve app: %w", err)
+	}
+	return app, nil
+}
+
+// validateAppConfig validates the app configuration
+func validateAppConfig(config models.AppConfig) error {
+	// Validate that at least one component is defined
+	if len(config.Components) == 0 {
+		return fmt.Errorf("app must define at least one component")
+	}
+
+	// Validate each component in the app
+	for i, component := range config.Components {
+		if component.Name == "" {
+			return fmt.Errorf("component at index %d must have a name", i)
+		}
+
+		// Validate resource configuration
+		if component.Resources.CPU == "" {
+			return fmt.Errorf("component '%s' must specify CPU resources", component.Name)
+		}
+		if component.Resources.Memory == "" {
+			return fmt.Errorf("component '%s' must specify memory resources", component.Name)
+		}
+	}
+
+	// Add additional validation logic as needed
+
+	return nil
+}

+ 3 - 3
services/clients.go

@@ -27,7 +27,7 @@ func (s *ClientService) CreateClient(client *models.Client) error {
 }
 
 // GetClient retrieves a client by ID
-func (s *ClientService) GetClient(id string) (*models.Client, error) {
+func (s *ClientService) GetClient(id int64) (*models.Client, error) {
 	client, err := s.store.GetByID(id)
 	if err != nil {
 		return nil, fmt.Errorf("failed to retrieve client: %w", err)
@@ -37,14 +37,14 @@ func (s *ClientService) GetClient(id string) (*models.Client, error) {
 
 // UpdateClient updates an existing client
 func (s *ClientService) UpdateClient(client *models.Client) error {
-	if client.ID == "" {
+	if client.ID == 0 {
 		return fmt.Errorf("client ID is required for update")
 	}
 	return s.store.Update(client)
 }
 
 // DeleteClient deletes a client by ID
-func (s *ClientService) DeleteClient(id string) error {
+func (s *ClientService) DeleteClient(id int64) error {
 	return s.store.Delete(id)
 }
 

+ 112 - 0
services/components.go

@@ -0,0 +1,112 @@
+package services
+
+import (
+	"fmt"
+
+	"git.linuxforward.com/byop/byop-engine/dbstore"
+	"git.linuxforward.com/byop/byop-engine/models"
+)
+
+// ComponentService handles business logic for components
+type ComponentService struct {
+	store *dbstore.ComponentStore
+}
+
+// NewComponentService creates a new ComponentService
+func NewComponentService(store *dbstore.ComponentStore) *ComponentService {
+	return &ComponentService{store: store}
+}
+
+// CreateComponent creates a new component
+func (s *ComponentService) CreateComponent(component *models.Component) error {
+	// Set default resource values if not provided
+	if component.Resources.CPU == "" {
+		component.Resources.CPU = "0.5"
+	}
+	if component.Resources.Memory == "" {
+		component.Resources.Memory = "512Mi"
+	}
+	if component.Resources.Storage == "" {
+		component.Resources.Storage = "1Gi"
+	}
+
+	// Set default scale settings if not provided
+	if component.ScaleSettings.MinInstances == 0 {
+		component.ScaleSettings.MinInstances = 1
+	}
+	if component.ScaleSettings.MaxInstances == 0 {
+		component.ScaleSettings.MaxInstances = 3
+	}
+	if component.ScaleSettings.CPUThreshold == 0 {
+		component.ScaleSettings.CPUThreshold = 80
+	}
+
+	// Persist the component
+	return s.store.Create(component)
+}
+
+// GetComponent retrieves a component by ID
+func (s *ComponentService) GetComponent(id int64) (*models.Component, error) {
+	component, err := s.store.GetByID(id)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve component: %w", err)
+	}
+	return component, nil
+}
+
+// UpdateComponent updates an existing component
+func (s *ComponentService) UpdateComponent(component *models.Component) error {
+	if component.ID == 0 {
+		return fmt.Errorf("component ID is required for update")
+	}
+
+	// Check if component exists
+	existingComponent, err := s.store.GetByID(component.ID)
+	if err != nil {
+		return fmt.Errorf("failed to check if component exists: %w", err)
+	}
+	if existingComponent == nil {
+		return fmt.Errorf("component with ID %d not found", component.ID)
+	}
+
+	return s.store.Update(component)
+}
+
+// DeleteComponent deletes a component by ID
+func (s *ComponentService) DeleteComponent(id int64) error {
+	// Check if component exists
+	component, err := s.store.GetByID(id)
+	if err != nil {
+		return fmt.Errorf("failed to check if component exists: %w", err)
+	}
+	if component == nil {
+		return fmt.Errorf("component with ID %d not found", id)
+	}
+
+	return s.store.Delete(id)
+}
+
+// ListComponents retrieves all components with optional filtering
+func (s *ComponentService) ListComponents(filter map[string]interface{}) ([]*models.Component, error) {
+	return s.store.List(filter)
+}
+
+// GetComponentDeployments retrieves all deployments for a component
+func (s *ComponentService) GetComponentDeployments(id int64) ([]models.DeployedApp, error) {
+	// First check if the component exists
+	component, err := s.store.GetByID(id)
+	if err != nil {
+		return nil, fmt.Errorf("failed to check if component exists: %w", err)
+	}
+	if component == nil {
+		return nil, fmt.Errorf("component with ID %d not found", id)
+	}
+
+	// Get component with deployments
+	componentWithDeployments, err := s.store.GetComponentWithDeployments(id)
+	if err != nil {
+		return nil, fmt.Errorf("failed to retrieve component deployments: %w", err)
+	}
+
+	return componentWithDeployments.Deployments, nil
+}

+ 66 - 66
services/deployments.go

@@ -7,39 +7,33 @@ import (
 
 	"git.linuxforward.com/byop/byop-engine/dbstore"
 	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
 )
 
 // DeploymentService handles business logic for deployments
 type DeploymentService struct {
-	store         *dbstore.DeploymentStore
-	appStore      *dbstore.AppStore
-	templateStore *dbstore.TemplateStore
-	clientStore   *dbstore.ClientStore
+	store          *dbstore.DeploymentStore
+	componentStore *dbstore.ComponentStore // Renamed from appStore
+	appStore       *dbstore.AppStore       // Renamed from templateStore
+	clientStore    *dbstore.ClientStore
 }
 
 // NewDeploymentService creates a new DeploymentService
 func NewDeploymentService(
 	store *dbstore.DeploymentStore,
+	componentStore *dbstore.ComponentStore,
 	appStore *dbstore.AppStore,
-	templateStore *dbstore.TemplateStore,
 	clientStore *dbstore.ClientStore,
 ) *DeploymentService {
 	return &DeploymentService{
-		store:         store,
-		appStore:      appStore,
-		templateStore: templateStore,
-		clientStore:   clientStore,
+		store:          store,
+		componentStore: componentStore,
+		appStore:       appStore,
+		clientStore:    clientStore,
 	}
 }
 
 // CreateDeployment creates a new deployment
 func (s *DeploymentService) CreateDeployment(deployment *models.Deployment) error {
-	// Generate UUID if not provided
-	if deployment.ID == "" {
-		deployment.ID = uuid.New().String()
-	}
-
 	// Validate the deployment
 	if err := s.validateDeployment(deployment); err != nil {
 		return fmt.Errorf("invalid deployment: %w", err)
@@ -70,7 +64,7 @@ func (s *DeploymentService) CreateDeployment(deployment *models.Deployment) erro
 }
 
 // GetDeployment retrieves a deployment by ID
-func (s *DeploymentService) GetDeployment(id string) (*models.Deployment, error) {
+func (s *DeploymentService) GetDeployment(id int64) (*models.Deployment, error) {
 	deployment, err := s.store.GetByID(id)
 	if err != nil {
 		return nil, fmt.Errorf("failed to retrieve deployment: %w", err)
@@ -89,7 +83,7 @@ func (s *DeploymentService) GetDeployment(id string) (*models.Deployment, error)
 // UpdateDeployment updates an existing deployment
 func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) error {
 	// Validate the deployment ID
-	if deployment.ID == "" {
+	if deployment.ID == 0 {
 		return fmt.Errorf("deployment ID is required for update")
 	}
 
@@ -99,7 +93,7 @@ func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) erro
 		return fmt.Errorf("failed to check if deployment exists: %w", err)
 	}
 	if existingDeployment == nil {
-		return fmt.Errorf("deployment with ID %s not found", deployment.ID)
+		return fmt.Errorf("deployment with ID %d not found", deployment.ID)
 	}
 
 	// Prevent updates to deployed apps if deployment is not in the right state
@@ -134,14 +128,14 @@ func (s *DeploymentService) UpdateDeployment(deployment *models.Deployment) erro
 }
 
 // DeleteDeployment deletes a deployment by ID
-func (s *DeploymentService) DeleteDeployment(id string) error {
+func (s *DeploymentService) DeleteDeployment(id int64) error {
 	// Check if deployment exists
 	deployment, err := s.store.GetByID(id)
 	if err != nil {
 		return fmt.Errorf("failed to check if deployment exists: %w", err)
 	}
 	if deployment == nil {
-		return fmt.Errorf("deployment with ID %s not found", id)
+		return fmt.Errorf("deployment with ID %d not found", id)
 	}
 
 	// Set status to deleting
@@ -175,14 +169,14 @@ func (s *DeploymentService) ListDeployments(filter map[string]interface{}) ([]*m
 }
 
 // GetDeploymentsByClientID retrieves deployments for a specific client
-func (s *DeploymentService) GetDeploymentsByClientID(clientID string) ([]*models.Deployment, error) {
+func (s *DeploymentService) GetDeploymentsByClientID(clientID int64) ([]*models.Deployment, error) {
 	// Check if client exists
 	client, err := s.clientStore.GetByID(clientID)
 	if err != nil {
 		return nil, fmt.Errorf("failed to check if client exists: %w", err)
 	}
 	if client == nil {
-		return nil, fmt.Errorf("client with ID %s not found", clientID)
+		return nil, fmt.Errorf("client with ID %d not found", clientID)
 	}
 
 	deployments, err := s.store.GetByClientID(clientID)
@@ -217,20 +211,20 @@ func (s *DeploymentService) GetDeploymentsByUserID(userID string) ([]*models.Dep
 	return deployments, nil
 }
 
-// GetDeploymentsByTemplateID retrieves deployments based on a specific template
-func (s *DeploymentService) GetDeploymentsByTemplateID(templateID string) ([]*models.Deployment, error) {
-	// Check if template exists
-	template, err := s.templateStore.GetByID(templateID)
+// GetDeploymentsByAppID retrieves deployments based on a specific app (was template)
+func (s *DeploymentService) GetDeploymentsByAppID(appID int64) ([]*models.Deployment, error) {
+	// Check if app exists
+	app, err := s.appStore.GetByID(appID)
 	if err != nil {
-		return nil, fmt.Errorf("failed to check if template exists: %w", err)
+		return nil, fmt.Errorf("failed to check if app exists: %w", err)
 	}
-	if template == nil {
-		return nil, fmt.Errorf("template with ID %s not found", templateID)
+	if app == nil {
+		return nil, fmt.Errorf("app with ID %d not found", appID)
 	}
 
-	deployments, err := s.store.GetByTemplateID(templateID)
+	deployments, err := s.store.GetByAppID(appID)
 	if err != nil {
-		return nil, fmt.Errorf("failed to retrieve deployments for template %s: %w", templateID, err)
+		return nil, fmt.Errorf("failed to retrieve deployments for app %s: %w", appID, err)
 	}
 
 	// Deserialize config fields for each deployment
@@ -244,7 +238,7 @@ func (s *DeploymentService) GetDeploymentsByTemplateID(templateID string) ([]*mo
 }
 
 // UpdateDeploymentStatus updates the status of a deployment
-func (s *DeploymentService) UpdateDeploymentStatus(id string, status string) error {
+func (s *DeploymentService) UpdateDeploymentStatus(id int64, status string) error {
 	deployment, err := s.store.GetByID(id)
 	if err != nil {
 		return fmt.Errorf("failed to retrieve deployment: %w", err)
@@ -276,7 +270,7 @@ func (s *DeploymentService) validateDeployment(deployment *models.Deployment) er
 	}
 
 	// Validate relationships
-	if deployment.ClientID == "" {
+	if deployment.ClientID == 0 {
 		return fmt.Errorf("client ID is required")
 	}
 	client, err := s.clientStore.GetByID(deployment.ClientID)
@@ -284,18 +278,18 @@ func (s *DeploymentService) validateDeployment(deployment *models.Deployment) er
 		return fmt.Errorf("failed to check client: %w", err)
 	}
 	if client == nil {
-		return fmt.Errorf("client with ID %s not found", deployment.ClientID)
+		return fmt.Errorf("client with ID %d not found", deployment.ClientID)
 	}
 
-	if deployment.TemplateID == "" {
-		return fmt.Errorf("template ID is required")
+	if deployment.AppID == 0 {
+		return fmt.Errorf("app ID is required")
 	}
-	template, err := s.templateStore.GetByID(deployment.TemplateID)
+	app, err := s.appStore.GetByID(deployment.AppID)
 	if err != nil {
-		return fmt.Errorf("failed to check template: %w", err)
+		return fmt.Errorf("failed to check app: %w", err)
 	}
-	if template == nil {
-		return fmt.Errorf("template with ID %s not found", deployment.TemplateID)
+	if app == nil {
+		return fmt.Errorf("app with ID %s not found", deployment.AppID)
 	}
 
 	return nil
@@ -308,46 +302,45 @@ func (s *DeploymentService) setupDeployedApps(deployment *models.Deployment) err
 		return nil
 	}
 
-	// Get the template
-	template, err := s.templateStore.GetByID(deployment.TemplateID)
+	// Get the app
+	app, err := s.appStore.GetByID(deployment.AppID)
 	if err != nil {
-		return fmt.Errorf("failed to retrieve template: %w", err)
+		return fmt.Errorf("failed to retrieve app: %w", err)
 	}
-	if template == nil {
-		return fmt.Errorf("template with ID %s not found", deployment.TemplateID)
+	if app == nil {
+		return fmt.Errorf("app with ID %d not found", deployment.AppID)
 	}
 
-	// Use the template config to set up deployed apps
-	var templateConfig models.TemplateConfig
-	if err := json.Unmarshal([]byte(template.ConfigJSON), &templateConfig); err != nil {
-		return fmt.Errorf("failed to parse template config: %w", err)
+	// Use the app config to set up deployed apps
+	var appConfig models.AppConfig
+	if err := json.Unmarshal([]byte(app.ConfigJSON), &appConfig); err != nil {
+		return fmt.Errorf("failed to parse app config: %w", err)
 	}
 
-	// Create deployed apps for each app in the template
-	for _, appConfig := range templateConfig.Apps {
-		// Get the app
-		app, err := s.appStore.GetByID(appConfig.ID)
+	// Create deployed apps for each component in the app
+	for _, componentConfig := range appConfig.Components {
+		// Get the component
+		component, err := s.componentStore.GetByID(componentConfig.ID)
 		if err != nil {
-			return fmt.Errorf("failed to retrieve app: %w", err)
+			return fmt.Errorf("failed to retrieve component: %w", err)
 		}
-		if app == nil {
-			return fmt.Errorf("app with ID %s not found", appConfig.ID)
+		if component == nil {
+			return fmt.Errorf("component with ID %d not found", componentConfig.ID)
 		}
 
-		// Create a deployed app
+		// Create a deployed app (GORM will auto-generate ID)
 		deployedApp := models.DeployedApp{
-			ID:           uuid.New().String(),
 			DeploymentID: deployment.ID,
-			AppID:        app.ID,
+			ComponentID:  component.ID,
 			Status:       string(models.PENDING_APP),
-			Version:      app.Version,
+			Version:      component.Version,
 			URL:          "", // Will be set during deployment
-			PodCount:     appConfig.Autoscaling.MinReplicas,
+			PodCount:     componentConfig.Autoscaling.MinReplicas,
 			HealthStatus: string(models.HEALTHY),
 			Resources: models.ResourceAllocation{
-				CPU:     appConfig.Resources.CPU,
-				Memory:  appConfig.Resources.Memory,
-				Storage: appConfig.Resources.Storage,
+				CPU:     componentConfig.Resources.CPU,
+				Memory:  componentConfig.Resources.Memory,
+				Storage: componentConfig.Resources.Storage,
 			},
 		}
 
@@ -359,7 +352,7 @@ func (s *DeploymentService) setupDeployedApps(deployment *models.Deployment) err
 }
 
 // processDeployment handles the actual deployment process
-func (s *DeploymentService) processDeployment(deploymentID string) {
+func (s *DeploymentService) processDeployment(deploymentID int64) {
 	// This would be an async process in a real system
 	// For now, we just update the status after a short delay to simulate the process
 
@@ -373,6 +366,13 @@ func (s *DeploymentService) processDeployment(deploymentID string) {
 	// 4. Setup monitoring
 	// etc.
 
+	// Logging the deployment process
+	fmt.Printf("Processing deployment %d...\n", deploymentID)
+	for i := 0; i < 5; i++ {
+		fmt.Printf("Deploying app %d/%d...\n", i+1, 5)
+		time.Sleep(500 * time.Millisecond) // Simulate work
+	}
+
 	// For this demo, we'll just update the status after a short delay
 	time.Sleep(2 * time.Second)
 
@@ -385,7 +385,7 @@ func (s *DeploymentService) processDeployment(deploymentID string) {
 }
 
 // processDeploymentCleanup handles the cleanup process for deleted deployments
-func (s *DeploymentService) processDeploymentCleanup(deploymentID string) {
+func (s *DeploymentService) processDeploymentCleanup(deploymentID int64) {
 	// This would be an async process in a real system
 	// In a real system, this would:
 	// 1. Deprovision infrastructure

+ 0 - 153
services/templates.go

@@ -1,153 +0,0 @@
-package services
-
-import (
-	"fmt"
-
-	"git.linuxforward.com/byop/byop-engine/dbstore"
-	"git.linuxforward.com/byop/byop-engine/models"
-	"github.com/google/uuid"
-)
-
-// TemplateService handles business logic for templates
-type TemplateService struct {
-	store *dbstore.TemplateStore
-}
-
-// NewTemplateService creates a new TemplateService
-func NewTemplateService(store *dbstore.TemplateStore) *TemplateService {
-	return &TemplateService{store: store}
-}
-
-// CreateTemplate creates a new deployment template
-func (s *TemplateService) CreateTemplate(template *models.Template) error {
-	// Generate UUID if not provided
-	if template.ID == "" {
-		template.ID = uuid.New().String()
-	}
-
-	// Validate template configuration
-	if err := validateTemplateConfig(template.Config); err != nil {
-		return fmt.Errorf("invalid template configuration: %w", err)
-	}
-
-	// Persist the template
-	return s.store.Create(template)
-}
-
-// GetTemplate retrieves a template by ID
-func (s *TemplateService) GetTemplate(id string) (*models.Template, error) {
-	template, err := s.store.GetByID(id)
-	if err != nil {
-		return nil, fmt.Errorf("failed to retrieve template: %w", err)
-	}
-	return template, nil
-}
-
-// UpdateTemplate updates an existing template
-func (s *TemplateService) UpdateTemplate(template *models.Template) error {
-	if template.ID == "" {
-		return fmt.Errorf("template ID is required for update")
-	}
-
-	// Check if template exists
-	existingTemplate, err := s.store.GetByID(template.ID)
-	if err != nil {
-		return fmt.Errorf("failed to check if template exists: %w", err)
-	}
-	if existingTemplate == nil {
-		return fmt.Errorf("template with ID %s not found", template.ID)
-	}
-
-	// Validate template configuration
-	if err := validateTemplateConfig(template.Config); err != nil {
-		return fmt.Errorf("invalid template configuration: %w", err)
-	}
-
-	return s.store.Update(template)
-}
-
-// DeleteTemplate deletes a template by ID
-func (s *TemplateService) DeleteTemplate(id string) error {
-	// Check if template exists
-	template, err := s.store.GetByID(id)
-	if err != nil {
-		return fmt.Errorf("failed to check if template exists: %w", err)
-	}
-	if template == nil {
-		return fmt.Errorf("template with ID %s not found", id)
-	}
-
-	// Check if the template has deployments
-	templateWithDeployments, err := s.store.GetTemplateWithDeployments(id)
-	if err != nil {
-		return fmt.Errorf("failed to check template deployments: %w", err)
-	}
-
-	// Don't allow deletion if there are active deployments
-	if len(templateWithDeployments.Deployments) > 0 {
-		return fmt.Errorf("cannot delete template with active deployments")
-	}
-
-	return s.store.Delete(id)
-}
-
-// ListTemplates retrieves all templates with optional filtering
-func (s *TemplateService) ListTemplates(filter map[string]interface{}) ([]*models.Template, error) {
-	return s.store.List(filter)
-}
-
-// GetTemplateDeployments retrieves all deployments for a template
-func (s *TemplateService) GetTemplateDeployments(id string) ([]models.Deployment, error) {
-	// First check if the template exists
-	template, err := s.store.GetByID(id)
-	if err != nil {
-		return nil, fmt.Errorf("failed to check if template exists: %w", err)
-	}
-	if template == nil {
-		return nil, fmt.Errorf("template with ID %s not found", id)
-	}
-
-	// Get template with deployments
-	templateWithDeployments, err := s.store.GetTemplateWithDeployments(id)
-	if err != nil {
-		return nil, fmt.Errorf("failed to retrieve template deployments: %w", err)
-	}
-
-	return templateWithDeployments.Deployments, nil
-}
-
-// GetTemplateByVersion retrieves a template by name and version
-func (s *TemplateService) GetTemplateByVersion(name, version string) (*models.Template, error) {
-	template, err := s.store.GetByVersion(name, version)
-	if err != nil {
-		return nil, fmt.Errorf("failed to retrieve template: %w", err)
-	}
-	return template, nil
-}
-
-// validateTemplateConfig validates the template configuration
-func validateTemplateConfig(config models.TemplateConfig) error {
-	// Validate that at least one app is defined
-	if len(config.Apps) == 0 {
-		return fmt.Errorf("template must define at least one app")
-	}
-
-	// Validate each app in the template
-	for i, app := range config.Apps {
-		if app.Name == "" {
-			return fmt.Errorf("app at index %d must have a name", i)
-		}
-
-		// Validate resource configuration
-		if app.Resources.CPU == "" {
-			return fmt.Errorf("app '%s' must specify CPU resources", app.Name)
-		}
-		if app.Resources.Memory == "" {
-			return fmt.Errorf("app '%s' must specify memory resources", app.Name)
-		}
-	}
-
-	// Add additional validation logic as needed
-
-	return nil
-}

+ 3 - 3
services/user.go

@@ -32,7 +32,7 @@ func (s *UserService) CreateUser(user *models.User) error {
 }
 
 // GetUser retrieves a user by ID
-func (s *UserService) GetUser(id string) (*models.User, error) {
+func (s *UserService) GetUser(id int64) (*models.User, error) {
 	user, err := s.store.GetByID(id)
 	if err != nil {
 		return nil, fmt.Errorf("failed to get user: %w", err)
@@ -56,7 +56,7 @@ func (s *UserService) UpdateUser(user *models.User) error {
 }
 
 // DeleteUser deletes a user by ID
-func (s *UserService) DeleteUser(id string) error {
+func (s *UserService) DeleteUser(id int64) error {
 	return s.store.Delete(id)
 }
 
@@ -88,7 +88,7 @@ func (s *UserService) AuthenticateUser(username, password string) (*models.User,
 }
 
 // GetUserDeployments retrieves all deployments for a user
-func (s *UserService) GetUserDeployments(userID string) ([]*models.Deployment, error) {
+func (s *UserService) GetUserDeployments(userID int64) ([]*models.Deployment, error) {
 	user, err := s.store.GetByID(userID)
 	if err != nil {
 		return nil, fmt.Errorf("failed to get user: %w", err)