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 }