Files
skybridge/faas/internal/repository/postgres/function_repository.go
2025-08-30 23:52:37 -04:00

268 lines
8.3 KiB
Go

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(&current.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
}