package postgres import ( "context" "database/sql" "encoding/json" "fmt" "github.com/google/uuid" "go.uber.org/zap" "github.com/RyanCopley/skybridge/faas/internal/domain" "github.com/RyanCopley/skybridge/faas/internal/repository" ) type functionRepository struct { db *sql.DB logger *zap.Logger } func NewFunctionRepository(db *sql.DB, logger *zap.Logger) repository.FunctionRepository { return &functionRepository{ db: db, logger: logger, } } func (r *functionRepository) Create(ctx context.Context, function *domain.FunctionDefinition) (*domain.FunctionDefinition, error) { envJSON, err := json.Marshal(function.Environment) if err != nil { return nil, fmt.Errorf("failed to marshal environment: %w", err) } query := ` INSERT INTO functions (id, name, app_id, runtime, image, handler, code, environment, timeout, memory, owner_type, owner_name, owner_owner, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15) RETURNING created_at, updated_at` timeoutValue, _ := function.Timeout.Value() err = r.db.QueryRowContext(ctx, query, function.ID, function.Name, function.AppID, function.Runtime, function.Image, function.Handler, function.Code, envJSON, timeoutValue, function.Memory, function.Owner.Type, function.Owner.Name, function.Owner.Owner, function.CreatedAt, function.UpdatedAt, ).Scan(&function.CreatedAt, &function.UpdatedAt) if err != nil { r.logger.Error("Failed to create function", zap.Error(err)) return nil, fmt.Errorf("failed to create function: %w", err) } return function, nil } func (r *functionRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.FunctionDefinition, error) { query := ` SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory, owner_type, owner_name, owner_owner, created_at, updated_at FROM functions WHERE id = $1` function := &domain.FunctionDefinition{} var envJSON []byte err := r.db.QueryRowContext(ctx, query, id).Scan( &function.ID, &function.Name, &function.AppID, &function.Runtime, &function.Image, &function.Handler, &function.Code, &envJSON, &function.Timeout, &function.Memory, &function.Owner.Type, &function.Owner.Name, &function.Owner.Owner, &function.CreatedAt, &function.UpdatedAt, ) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("function not found") } r.logger.Error("Failed to get function by ID", zap.String("id", id.String()), zap.Error(err)) return nil, fmt.Errorf("failed to get function: %w", err) } // Unmarshal environment if err := json.Unmarshal(envJSON, &function.Environment); err != nil { return nil, fmt.Errorf("failed to unmarshal environment: %w", err) } return function, nil } func (r *functionRepository) GetByName(ctx context.Context, appID, name string) (*domain.FunctionDefinition, error) { query := ` SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory, owner_type, owner_name, owner_owner, created_at, updated_at FROM functions WHERE app_id = $1 AND name = $2` function := &domain.FunctionDefinition{} var envJSON []byte err := r.db.QueryRowContext(ctx, query, appID, name).Scan( &function.ID, &function.Name, &function.AppID, &function.Runtime, &function.Image, &function.Handler, &function.Code, &envJSON, &function.Timeout, &function.Memory, &function.Owner.Type, &function.Owner.Name, &function.Owner.Owner, &function.CreatedAt, &function.UpdatedAt, ) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("function not found") } r.logger.Error("Failed to get function by name", zap.String("app_id", appID), zap.String("name", name), zap.Error(err)) return nil, fmt.Errorf("failed to get function: %w", err) } // Unmarshal environment if err := json.Unmarshal(envJSON, &function.Environment); err != nil { return nil, fmt.Errorf("failed to unmarshal environment: %w", err) } return function, nil } func (r *functionRepository) Update(ctx context.Context, id uuid.UUID, updates *domain.UpdateFunctionRequest) (*domain.FunctionDefinition, error) { // First get the current function current, err := r.GetByID(ctx, id) if err != nil { return nil, err } // Apply updates if updates.Name != nil { current.Name = *updates.Name } if updates.Runtime != nil { current.Runtime = *updates.Runtime } if updates.Image != nil { current.Image = *updates.Image } if updates.Handler != nil { current.Handler = *updates.Handler } if updates.Code != nil { current.Code = *updates.Code } if updates.Environment != nil { current.Environment = updates.Environment } if updates.Timeout != nil { current.Timeout = *updates.Timeout } if updates.Memory != nil { current.Memory = *updates.Memory } if updates.Owner != nil { current.Owner = *updates.Owner } // Marshal environment envJSON, err := json.Marshal(current.Environment) if err != nil { return nil, fmt.Errorf("failed to marshal environment: %w", err) } query := ` UPDATE functions SET name = $2, runtime = $3, image = $4, handler = $5, code = $6, environment = $7, timeout = $8, memory = $9, owner_type = $10, owner_name = $11, owner_owner = $12, updated_at = CURRENT_TIMESTAMP WHERE id = $1 RETURNING updated_at` timeoutValue, _ := current.Timeout.Value() err = r.db.QueryRowContext(ctx, query, id, current.Name, current.Runtime, current.Image, current.Handler, current.Code, envJSON, timeoutValue, current.Memory, current.Owner.Type, current.Owner.Name, current.Owner.Owner, ).Scan(¤t.UpdatedAt) if err != nil { r.logger.Error("Failed to update function", zap.String("id", id.String()), zap.Error(err)) return nil, fmt.Errorf("failed to update function: %w", err) } return current, nil } func (r *functionRepository) Delete(ctx context.Context, id uuid.UUID) error { query := `DELETE FROM functions WHERE id = $1` result, err := r.db.ExecContext(ctx, query, id) if err != nil { r.logger.Error("Failed to delete function", zap.String("id", id.String()), zap.Error(err)) return fmt.Errorf("failed to delete function: %w", err) } rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to get affected rows: %w", err) } if rowsAffected == 0 { return fmt.Errorf("function not found") } return nil } func (r *functionRepository) List(ctx context.Context, appID string, limit, offset int) ([]*domain.FunctionDefinition, error) { var query string var args []interface{} if appID != "" { query = ` SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory, owner_type, owner_name, owner_owner, created_at, updated_at FROM functions WHERE app_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3` args = []interface{}{appID, limit, offset} } else { query = ` SELECT id, name, app_id, runtime, image, handler, code, environment, timeout, memory, owner_type, owner_name, owner_owner, created_at, updated_at FROM functions ORDER BY created_at DESC LIMIT $1 OFFSET $2` args = []interface{}{limit, offset} } rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { r.logger.Error("Failed to list functions", zap.Error(err)) return nil, fmt.Errorf("failed to list functions: %w", err) } defer rows.Close() var functions []*domain.FunctionDefinition for rows.Next() { function := &domain.FunctionDefinition{} var envJSON []byte err := rows.Scan( &function.ID, &function.Name, &function.AppID, &function.Runtime, &function.Image, &function.Handler, &function.Code, &envJSON, &function.Timeout, &function.Memory, &function.Owner.Type, &function.Owner.Name, &function.Owner.Owner, &function.CreatedAt, &function.UpdatedAt, ) if err != nil { r.logger.Error("Failed to scan function", zap.Error(err)) return nil, fmt.Errorf("failed to scan function: %w", err) } // Unmarshal environment if err := json.Unmarshal(envJSON, &function.Environment); err != nil { return nil, fmt.Errorf("failed to unmarshal environment: %w", err) } functions = append(functions, function) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("failed to iterate functions: %w", err) } return functions, nil } func (r *functionRepository) GetByAppID(ctx context.Context, appID string) ([]*domain.FunctionDefinition, error) { return r.List(ctx, appID, 1000, 0) // Get all functions for the app }