eintopf.info@v0.13.16/service/action/action.go (about)

     1  // Copyright (C) 2024 The Eintopf authors
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  
    16  package action
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"log"
    22  	"time"
    23  
    24  	"github.com/go-co-op/gocron/v2"
    25  
    26  	"eintopf.info/internal/crud"
    27  	"eintopf.info/service/auth"
    28  )
    29  
    30  type Action struct {
    31  	Name         string    `json:"name"`
    32  	Calls        int       `json:"calls"`
    33  	LastCall     time.Time `json:"lastCall" db:"last_call"`
    34  	LastCalledBy string    `json:"lastCalledBy" db:"last_called_by"`
    35  	Error        string    `json:"error"`
    36  }
    37  
    38  func (a Action) Identifier() string { return a.Name }
    39  
    40  type Function func(ctx context.Context) error
    41  
    42  type (
    43  	Filters struct{}
    44  	Storer  crud.Storer[Action, Action, Filters]
    45  )
    46  
    47  type Service struct {
    48  	actions map[string]Function
    49  
    50  	store    Storer
    51  	sheduler gocron.Scheduler
    52  }
    53  
    54  func NewService(store Storer, sheduler gocron.Scheduler) *Service {
    55  	return &Service{
    56  		actions:  map[string]Function{},
    57  		store:    store,
    58  		sheduler: sheduler,
    59  	}
    60  }
    61  
    62  func (s *Service) Register(name string, fn Function, cronExpression string) error {
    63  	s.actions[name] = fn
    64  	ctx := auth.ContextWithID(auth.ContextWithRole(context.Background(), auth.RoleInternal), "internal")
    65  
    66  	if cronExpression != "" {
    67  		_, err := s.sheduler.NewJob(gocron.CronJob(cronExpression, false), gocron.NewTask(func() {
    68  			err := s.Call(ctx, name)
    69  			if err != nil {
    70  				log.Printf("cronjob: %s", err)
    71  			}
    72  		}))
    73  		if err != nil {
    74  			return err
    75  		}
    76  	}
    77  
    78  	action, err := s.store.FindByID(ctx, name)
    79  	if err != nil {
    80  		return err
    81  	}
    82  	if action != nil {
    83  		// Prevent the action from getting registered multiple times.
    84  		return nil
    85  	}
    86  
    87  	_, err = s.store.Create(ctx, &Action{Name: name})
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (s *Service) Call(ctx context.Context, name string) error {
    96  	fn, ok := s.actions[name]
    97  	if !ok {
    98  		return fmt.Errorf("action was not registered: %s", name)
    99  	}
   100  	callError := fn(ctx)
   101  	if callError != nil {
   102  		log.Printf("action(%s) error: %s", name, callError)
   103  	}
   104  
   105  	action, err := s.store.FindByID(ctx, name)
   106  	if err != nil {
   107  		return fmt.Errorf("action not found: %s", err)
   108  	}
   109  	action.Calls += 1
   110  	action.LastCall = time.Now()
   111  	calledBy, err := auth.UserIDFromContext(ctx)
   112  	if err == nil {
   113  		if calledBy == "internal" {
   114  			action.LastCalledBy = "internal"
   115  		} else {
   116  			action.LastCalledBy = fmt.Sprintf("id:%s", calledBy)
   117  		}
   118  	}
   119  	if callError != nil {
   120  		action.Error = callError.Error()
   121  	} else {
   122  		action.Error = ""
   123  	}
   124  	_, err = s.store.Update(ctx, action)
   125  	if err != nil {
   126  		return fmt.Errorf("action update: %s", err)
   127  	}
   128  	return callError
   129  }
   130  
   131  func (s *Service) Store() Storer {
   132  	return s.store
   133  }