From 50e1bf577f69129d61c09cde5cee109ae590b27b Mon Sep 17 00:00:00 2001 From: Ismael Ibuan <102030576+iibuan@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:23:14 +0800 Subject: [PATCH 1/3] refactor callback Signed-off-by: Ismael Ibuan <102030576+iibuan@users.noreply.github.com> --- src/goapp/config/config.go | 1 + src/goapp/config/env-config.go | 4 + src/goapp/init.go | 3 + src/goapp/main.go | 18 +--- .../item/item-repository-interface.go | 1 + src/goapp/repository/item/item-repository.go | 20 +++++ .../service/item/item-service-interface.go | 1 + src/goapp/service/item/item-service.go | 8 ++ src/goapp/timed-jobs/timed-jobs-interface.go | 5 ++ src/goapp/timed-jobs/timed-jobs.go | 83 +++++++++++++++++++ 10 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 src/goapp/timed-jobs/timed-jobs-interface.go create mode 100644 src/goapp/timed-jobs/timed-jobs.go diff --git a/src/goapp/config/config.go b/src/goapp/config/config.go index d6929cf..064b375 100644 --- a/src/goapp/config/config.go +++ b/src/goapp/config/config.go @@ -20,4 +20,5 @@ type ConfigManager interface { GetLinkFooters() string GetOrganizationName() string GetCommunityPortalAppId() string + GetCallbackRetryFreq() string } diff --git a/src/goapp/config/env-config.go b/src/goapp/config/env-config.go index c192a9f..7f4e457 100644 --- a/src/goapp/config/env-config.go +++ b/src/goapp/config/env-config.go @@ -78,3 +78,7 @@ func (ecm *envConfigManager) GetOrganizationName() string { func (ecm *envConfigManager) GetCommunityPortalAppId() string { return os.Getenv("COMMUNITY_PORTAL_APP_ID") } + +func (ecm *envConfigManager) GetCallbackRetryFreq() string { + return os.Getenv("CALLBACK_RETRY_FREQ") +} diff --git a/src/goapp/init.go b/src/goapp/init.go index 28647da..43d2f7e 100644 --- a/src/goapp/init.go +++ b/src/goapp/init.go @@ -8,6 +8,7 @@ import ( c "main/controller" r "main/repository" s "main/service" + t "main/timed-jobs" ) var ( @@ -38,5 +39,7 @@ var ( c.NewItemPageController(svc, conf), ) + timedJobs = t.NewTimedJobs(svc, conf) + httpRouter router.Router = router.NewMuxRouter() ) diff --git a/src/goapp/main.go b/src/goapp/main.go index 20e9ec5..0908fd4 100644 --- a/src/goapp/main.go +++ b/src/goapp/main.go @@ -3,11 +3,6 @@ package main import ( "log" session "main/pkg/session" - rtApprovals "main/routes/pages/approvals" - "strconv" - "time" - - ev "main/pkg/envvar" "github.com/joho/godotenv" ) @@ -22,21 +17,10 @@ func main() { // Create session and GitHubClient session.InitializeSession() - go checkFailedCallbacks() + go timedJobs.ReprocessFailedCallbacks() setPageRoutes() setApiRoutes() serve() } - -func checkFailedCallbacks() { - // TIMER SERVICE - freq := ev.GetEnvVar("CALLBACK_RETRY_FREQ", "15") - freqInt, _ := strconv.ParseInt(freq, 0, 64) - if freq > "0" { - for range time.NewTicker(time.Duration(freqInt) * time.Minute).C { - rtApprovals.ProcessFailedCallbacks() - } - } -} diff --git a/src/goapp/repository/item/item-repository-interface.go b/src/goapp/repository/item/item-repository-interface.go index 60ddf81..e7befba 100644 --- a/src/goapp/repository/item/item-repository-interface.go +++ b/src/goapp/repository/item/item-repository-interface.go @@ -5,6 +5,7 @@ import ( ) type ItemRepository interface { + GetFailedCallbacks() ([]string, error) GetItemById(id string) (*model.Item, error) GetItemsBy(itemOptions model.ItemOptions) ([]model.Item, error) GetTotalItemsBy(itemOptions model.ItemOptions) (int, error) diff --git a/src/goapp/repository/item/item-repository.go b/src/goapp/repository/item/item-repository.go index 6b12d3b..87607bf 100644 --- a/src/goapp/repository/item/item-repository.go +++ b/src/goapp/repository/item/item-repository.go @@ -19,6 +19,26 @@ func NewItemRepository(db *db.Database) ItemRepository { } } +func (r *itemRepository) GetFailedCallbacks() ([]string, error) { + row, err := r.Query("PR_Items_Select_FailedCallbacks") + if err != nil { + return []string{}, err + } + defer row.Close() + + result, err := r.RowsToMap(row) + if err != nil { + return []string{}, err + } + + var failedCallbacks []string + for _, v := range result { + failedCallbacks = append(failedCallbacks, v["Id"].(string)) + } + + return failedCallbacks, nil +} + func (r *itemRepository) GetItemById(id string) (*model.Item, error) { row, err := r.Query("PR_Items_Select_ById", sql.Named("Id", id)) if err != nil { diff --git a/src/goapp/service/item/item-service-interface.go b/src/goapp/service/item/item-service-interface.go index da2a83b..f267fd8 100644 --- a/src/goapp/service/item/item-service-interface.go +++ b/src/goapp/service/item/item-service-interface.go @@ -5,6 +5,7 @@ import ( ) type ItemService interface { + GetFailedCallbacks() ([]string, error) GetItemById(id string) (*model.Item, error) GetAll(itemOptions model.ItemOptions) (model.Response, error) InsertItem(item model.ItemInsertRequest) (string, error) diff --git a/src/goapp/service/item/item-service.go b/src/goapp/service/item/item-service.go index 5c368dd..1715218 100644 --- a/src/goapp/service/item/item-service.go +++ b/src/goapp/service/item/item-service.go @@ -21,6 +21,14 @@ func NewItemService(repo *repository.Repository, configManager config.ConfigMana } } +func (s *itemService) GetFailedCallbacks() ([]string, error) { + failedCallbacks, err := s.Repository.Item.GetFailedCallbacks() + if err != nil { + return []string{}, err + } + return failedCallbacks, nil +} + func (s *itemService) GetItemById(id string) (*model.Item, error) { item, err := s.Repository.Item.GetItemById(id) if err != nil { diff --git a/src/goapp/timed-jobs/timed-jobs-interface.go b/src/goapp/timed-jobs/timed-jobs-interface.go new file mode 100644 index 0000000..9a18d86 --- /dev/null +++ b/src/goapp/timed-jobs/timed-jobs-interface.go @@ -0,0 +1,5 @@ +package timedjobs + +type TimedJobs interface { + ReprocessFailedCallbacks() +} diff --git a/src/goapp/timed-jobs/timed-jobs.go b/src/goapp/timed-jobs/timed-jobs.go new file mode 100644 index 0000000..ddd9166 --- /dev/null +++ b/src/goapp/timed-jobs/timed-jobs.go @@ -0,0 +1,83 @@ +package timedjobs + +import ( + "bytes" + "encoding/json" + "fmt" + "main/config" + "main/model" + "main/service" + "net/http" + "strconv" + "time" +) + +type timedJobs struct { + Service *service.Service + configManager config.ConfigManager +} + +func NewTimedJobs(s *service.Service, configManager config.ConfigManager) TimedJobs { + return &timedJobs{ + Service: s, + configManager: configManager, + } +} + +func (t *timedJobs) ReprocessFailedCallbacks() { + freq := t.configManager.GetCallbackRetryFreq() + freqInt, _ := strconv.ParseInt(freq, 0, 64) + if freqInt > 0 { + for range time.NewTicker(time.Duration(freqInt) * time.Minute).C { + f, err := t.Service.Item.GetFailedCallbacks() + if err != nil { + fmt.Printf("Failed to get failed callbacks: %v", err.Error()) + return + } + + for _, id := range f { + go t.postCallback(id) + } + } + } +} + +func (t *timedJobs) postCallback(id string) { + item, err := t.Service.Item.GetItemById(id) + if err != nil { + fmt.Println("Error getting item by id: ", id) + return + } + + if item.CallbackUrl == "" { + fmt.Println("No callback url found") + return + } else { + params := model.ResponseCallback{ + ItemId: id, + IsApproved: item.IsApproved, + Remarks: item.ApproverRemarks, + ResponseDate: item.DateResponded, + RespondedBy: item.RespondedBy, + } + + jsonReq, err := json.Marshal(params) + if err != nil { + return + } + + res, err := http.Post(item.CallbackUrl, "application/json", bytes.NewBuffer(jsonReq)) + if err != nil { + fmt.Println("Error posting callback: ", err) + return + } + + isCallbackFailed := res.StatusCode != 200 + + err = t.Service.Item.UpdateItemCallback(id, isCallbackFailed) + if err != nil { + fmt.Println("Error updating item callback: ", err) + return + } + } +} From d88f4449c72fcdd9ab8ed637a51db2abdfece752 Mon Sep 17 00:00:00 2001 From: Ismael Ibuan <102030576+iibuan@users.noreply.github.com> Date: Wed, 16 Oct 2024 23:25:52 +0800 Subject: [PATCH 2/3] remove old codes Signed-off-by: Ismael Ibuan <102030576+iibuan@users.noreply.github.com> --- src/goapp/routes/pages/approvals/response.go | 104 ------------------- 1 file changed, 104 deletions(-) delete mode 100644 src/goapp/routes/pages/approvals/response.go diff --git a/src/goapp/routes/pages/approvals/response.go b/src/goapp/routes/pages/approvals/response.go deleted file mode 100644 index a206ee2..0000000 --- a/src/goapp/routes/pages/approvals/response.go +++ /dev/null @@ -1,104 +0,0 @@ -package route - -import ( - "bytes" - "encoding/json" - "fmt" - "main/pkg/sql" - "net/http" - "os" - "time" -) - -func connectSql() (db *sql.DB) { - db, err := sql.Init(sql.ConnectionParam{ConnectionString: os.Getenv("APPROVALSYSTEMDB_CONNECTION_STRING")}) - if err != nil { - fmt.Printf("ERROR: %+v", err) - } - return -} - -func handleError(err error) { - if err != nil { - fmt.Printf("ERROR: %+v", err) - } -} - -func ProcessFailedCallbacks() { - db := connectSql() - defer db.Close() - res, err := db.ExecuteStoredProcedureWithResult("PR_Items_Select_FailedCallbacks", nil) - handleError(err) - - for _, i := range res { - go postCallback(i["Id"].(string)) - } -} - -func postCallback(itemId string) { - db := connectSql() - defer db.Close() - - queryParams := map[string]interface{}{ - "Id": itemId, - } - res, err := db.ExecuteStoredProcedureWithResult("PR_Items_Select_ById", queryParams) - handleError(err) - approvalDate := res[0]["DateResponded"].(time.Time) - - callbackUrl := res[0]["CallbackUrl"].(string) - - if callbackUrl != "" { - postParams := TypPostParams{ - ItemId: itemId, - IsApproved: res[0]["IsApproved"].(bool), - Remarks: res[0]["ApproverRemarks"].(string), - ResponseDate: approvalDate.Format("2006-01-02T15:04:05.000Z"), - RespondedBy: res[0]["RespondedBy"].(string), - } - - ch := make(chan *http.Response) - - // var res *http.Response - - go getHttpPostResponseStatus(callbackUrl, postParams, ch) - - res := <-ch - - isCallbackFailed := true - if res != nil { - if res.StatusCode == 200 { - isCallbackFailed = false - } - } - - params := map[string]interface{}{ - "ItemId": itemId, - "IsCallbackFailed": isCallbackFailed, - } - db.ExecuteStoredProcedure("PR_Items_Update_Callback", params) - - } - -} - -func getHttpPostResponseStatus(url string, data interface{}, ch chan *http.Response) { - jsonReq, err := json.Marshal(data) - if err != nil { - ch <- nil - } - - res, err := http.Post(url, "application/json", bytes.NewBuffer(jsonReq)) - if err != nil { - ch <- nil - } - ch <- res -} - -type TypPostParams struct { - ItemId string `json:"itemId"` - IsApproved bool `json:"isApproved"` - Remarks string `json:"remarks"` - ResponseDate string `json:"responseDate"` - RespondedBy string `json:"respondedBy"` -} From a9be85980d8af21a92c59924c15df278c7a492d6 Mon Sep 17 00:00:00 2001 From: Ismael Ibuan <102030576+iibuan@users.noreply.github.com> Date: Thu, 17 Oct 2024 16:51:19 +0800 Subject: [PATCH 3/3] add CALLBACK_RETRY_FREQ Signed-off-by: Ismael Ibuan <102030576+iibuan@users.noreply.github.com> --- .bicep/webapp/parameters.json | 3 ++- .github/workflows/setup-appservice-resource.yml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.bicep/webapp/parameters.json b/.bicep/webapp/parameters.json index 731629e..c5fd576 100644 --- a/.bicep/webapp/parameters.json +++ b/.bicep/webapp/parameters.json @@ -42,7 +42,8 @@ "EMAIL_USER_ID" : "", "LINK_FOOTERS": "", "ORGANIZATION_NAME": "", - "COMMUNITY_PORTAL_APP_ID": "" + "COMMUNITY_PORTAL_APP_ID": "", + "CALLBACK_RETRY_FREQ": "" } } } diff --git a/.github/workflows/setup-appservice-resource.yml b/.github/workflows/setup-appservice-resource.yml index c61db92..e1c3daf 100644 --- a/.github/workflows/setup-appservice-resource.yml +++ b/.github/workflows/setup-appservice-resource.yml @@ -46,6 +46,7 @@ jobs: parameters.appServiceSettings.value.DOCKER_REGISTRY_SERVER_PASSWORD : ${{ secrets.CONTAINER_REGISTRY_SERVER_PASSWORD }} parameters.appServiceSettings.value.APPROVALSYSTEMDB_CONNECTION_STRING : ${{ secrets.DATABASE_CONNECTION_STRING }} parameters.appServiceSettings.value.COMMUNITY_PORTAL_APP_ID : ${{ vars.COMMUNITY_PORTAL_APP_ID }} + parameters.appServiceSettings.value.CALLBACK_RETRY_FREQ: ${{ vars.CALLBACK_RETRY_FREQ }} - name: Deploy App Service Plan and Web App uses: azure/arm-deploy@v1