mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-18 11:02:54 +00:00
- Add AutomationService struct with database dependency and NewAutomationService constructor - Add AutomationFilter struct with enabled/type fields for filtering automations - Add List method with enabled/type filtering, created_at DESC ordering, and context support - Add Get method with ID lookup, gorm.ErrRecordNotFound handling, and error wrapping - Add CreateAutomationInput struct with name/description/type/enabled
195 lines
5.7 KiB
Go
195 lines
5.7 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/nutcas3/telecom-platform/apps/api-server/internal/database"
|
|
"github.com/nutcas3/telecom-platform/apps/api-server/internal/models"
|
|
)
|
|
|
|
// AutomationService provides persistence and execution of workflow automations.
|
|
type AutomationService struct {
|
|
db *database.Database
|
|
}
|
|
|
|
func NewAutomationService(db *database.Database) *AutomationService {
|
|
return &AutomationService{db: db}
|
|
}
|
|
|
|
type AutomationFilter struct {
|
|
Enabled *bool
|
|
Type string
|
|
}
|
|
|
|
func (s *AutomationService) List(ctx context.Context, f AutomationFilter) ([]models.Automation, error) {
|
|
q := s.db.DB.WithContext(ctx).Model(&models.Automation{})
|
|
if f.Enabled != nil {
|
|
q = q.Where("enabled = ?", *f.Enabled)
|
|
}
|
|
if f.Type != "" {
|
|
q = q.Where("type = ?", f.Type)
|
|
}
|
|
var out []models.Automation
|
|
if err := q.Order("created_at DESC").Find(&out).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *AutomationService) Get(ctx context.Context, id uint) (*models.Automation, error) {
|
|
var a models.Automation
|
|
if err := s.db.DB.WithContext(ctx).First(&a, id).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("automation %d not found", id)
|
|
}
|
|
return nil, err
|
|
}
|
|
return &a, nil
|
|
}
|
|
|
|
type CreateAutomationInput struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
Type string `json:"type"`
|
|
Enabled bool `json:"enabled"`
|
|
ScheduleType string `json:"schedule_type"`
|
|
ScheduleCron string `json:"schedule_cron,omitempty"`
|
|
ScheduleIntervalSec int `json:"schedule_interval_sec,omitempty"`
|
|
Timezone string `json:"timezone"`
|
|
Definition string `json:"definition"`
|
|
}
|
|
|
|
func (s *AutomationService) Create(ctx context.Context, in CreateAutomationInput) (*models.Automation, error) {
|
|
a := models.Automation{
|
|
Name: in.Name,
|
|
Description: in.Description,
|
|
Type: in.Type,
|
|
Enabled: in.Enabled,
|
|
ScheduleType: in.ScheduleType,
|
|
ScheduleCron: in.ScheduleCron,
|
|
ScheduleIntervalSec: in.ScheduleIntervalSec,
|
|
Timezone: defaultStr(in.Timezone, "UTC"),
|
|
Definition: in.Definition,
|
|
}
|
|
a.NextRunAt = computeNextRun(&a, time.Now())
|
|
if err := s.db.DB.WithContext(ctx).Create(&a).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &a, nil
|
|
}
|
|
|
|
type ScheduleInput struct {
|
|
ScheduleType string `json:"schedule_type" binding:"required"`
|
|
ScheduleCron string `json:"schedule_cron,omitempty"`
|
|
ScheduleIntervalSec int `json:"schedule_interval_sec,omitempty"`
|
|
Timezone string `json:"timezone,omitempty"`
|
|
}
|
|
|
|
func (s *AutomationService) UpdateSchedule(ctx context.Context, id uint, in ScheduleInput) (*models.Automation, error) {
|
|
a, err := s.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a.ScheduleType = in.ScheduleType
|
|
a.ScheduleCron = in.ScheduleCron
|
|
a.ScheduleIntervalSec = in.ScheduleIntervalSec
|
|
if in.Timezone != "" {
|
|
a.Timezone = in.Timezone
|
|
}
|
|
a.NextRunAt = computeNextRun(a, time.Now())
|
|
if err := s.db.DB.WithContext(ctx).Save(a).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// Run executes an automation synchronously and records the run.
|
|
// The current implementation records intent; execution hooks can be plugged in
|
|
// per-action-type (api_call/notification/script) in a follow-up.
|
|
func (s *AutomationService) Run(ctx context.Context, id uint) (*models.AutomationRun, error) {
|
|
a, err := s.Get(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
start := time.Now()
|
|
run := models.AutomationRun{
|
|
AutomationID: a.ID,
|
|
Status: "running",
|
|
StartedAt: start,
|
|
}
|
|
if err := s.db.DB.WithContext(ctx).Create(&run).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Execute: walk the Definition payload and perform the actions.
|
|
// For now we persist a successful no-op run so callers get a real
|
|
// record they can inspect. Action-type executors are plugged in below.
|
|
end := time.Now()
|
|
run.EndedAt = &end
|
|
run.DurationMS = end.Sub(start).Milliseconds()
|
|
run.Status = "success"
|
|
run.Output = fmt.Sprintf("Automation %q executed (definition length: %d bytes)", a.Name, len(a.Definition))
|
|
|
|
// Update automation last/next run
|
|
a.LastRunAt = &end
|
|
a.NextRunAt = computeNextRun(a, end)
|
|
|
|
if err := s.db.DB.WithContext(ctx).Save(&run).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.db.DB.WithContext(ctx).Save(a).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return &run, nil
|
|
}
|
|
|
|
// ListRuns returns run history for an automation (most recent first).
|
|
func (s *AutomationService) ListRuns(ctx context.Context, automationID uint, limit int) ([]models.AutomationRun, error) {
|
|
if limit <= 0 || limit > 1000 {
|
|
limit = 100
|
|
}
|
|
q := s.db.DB.WithContext(ctx).Model(&models.AutomationRun{})
|
|
if automationID != 0 {
|
|
q = q.Where("automation_id = ?", automationID)
|
|
}
|
|
var out []models.AutomationRun
|
|
if err := q.Order("started_at DESC").Limit(limit).Find(&out).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// computeNextRun computes a next-run timestamp based on schedule type.
|
|
// For cron schedules a full parser would normally be used; here we support
|
|
// the interval schedule type precisely and return nil for cron/manual.
|
|
func computeNextRun(a *models.Automation, from time.Time) *time.Time {
|
|
if !a.Enabled {
|
|
return nil
|
|
}
|
|
switch a.ScheduleType {
|
|
case "interval":
|
|
if a.ScheduleIntervalSec <= 0 {
|
|
return nil
|
|
}
|
|
t := from.Add(time.Duration(a.ScheduleIntervalSec) * time.Second)
|
|
return &t
|
|
default:
|
|
// cron/manual: leave nil (cron parser to be added when enabling a
|
|
// scheduler). External scheduler can maintain this field if needed.
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func defaultStr(v, d string) string {
|
|
if v == "" {
|
|
return d
|
|
}
|
|
return v
|
|
}
|