From 6edfa6bc88ed73fc5a31f1b2fe9f5587c932ada7 Mon Sep 17 00:00:00 2001
From: Lunny Xiao <xiaolunwen@gmail.com>
Date: Thu, 10 Dec 2020 01:20:13 +0800
Subject: [PATCH] Fix broken migration on webhook (#13911)

* Fix broken migration on webhook

* Fix lint

Co-authored-by: John Olheiser <john.olheiser@gmail.com>
---
 models/migrations/migrations.go |  2 ++
 models/migrations/v162.go       | 59 +++++++++++++++++++++++++++++++++
 models/repo_generate.go         | 22 ++++++------
 models/webhook.go               | 10 +++---
 modules/convert/convert.go      |  4 +--
 routers/api/v1/utils/hook.go    |  8 ++---
 routers/repo/webhook.go         | 22 ++++++------
 services/webhook/webhook.go     |  8 ++---
 8 files changed, 98 insertions(+), 37 deletions(-)
 create mode 100644 models/migrations/v162.go

diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index d2ad20a36..cac36edf5 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -267,6 +267,8 @@ var migrations = []Migration{
 	NewMigration("Add block on official review requests branch protection", addBlockOnOfficialReviewRequests),
 	// v161 -> v162
 	NewMigration("Convert task type from int to string", convertTaskTypeToString),
+	// v162 -> v163
+	NewMigration("Convert webhook task type from int to string", convertWebhookTaskTypeToString),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v162.go b/models/migrations/v162.go
new file mode 100644
index 000000000..b65eb4cd9
--- /dev/null
+++ b/models/migrations/v162.go
@@ -0,0 +1,59 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"xorm.io/xorm"
+)
+
+func convertWebhookTaskTypeToString(x *xorm.Engine) error {
+	const (
+		GOGS int = iota + 1
+		SLACK
+		GITEA
+		DISCORD
+		DINGTALK
+		TELEGRAM
+		MSTEAMS
+		FEISHU
+		MATRIX
+	)
+
+	var hookTaskTypes = map[int]string{
+		GITEA:    "gitea",
+		GOGS:     "gogs",
+		SLACK:    "slack",
+		DISCORD:  "discord",
+		DINGTALK: "dingtalk",
+		TELEGRAM: "telegram",
+		MSTEAMS:  "msteams",
+		FEISHU:   "feishu",
+		MATRIX:   "matrix",
+	}
+
+	type Webhook struct {
+		Type string `xorm:"char(16) index"`
+	}
+	if err := x.Sync2(new(Webhook)); err != nil {
+		return err
+	}
+
+	for i, s := range hookTaskTypes {
+		if _, err := x.Exec("UPDATE webhook set type = ? where hook_task_type=?", s, i); err != nil {
+			return err
+		}
+	}
+
+	sess := x.NewSession()
+	defer sess.Close()
+	if err := sess.Begin(); err != nil {
+		return err
+	}
+	if err := dropTableColumns(sess, "webhook", "hook_task_type"); err != nil {
+		return err
+	}
+
+	return sess.Commit()
+}
diff --git a/models/repo_generate.go b/models/repo_generate.go
index 0b234d8e3..b0016494c 100644
--- a/models/repo_generate.go
+++ b/models/repo_generate.go
@@ -117,17 +117,17 @@ func GenerateWebhooks(ctx DBContext, templateRepo, generateRepo *Repository) err
 
 	for _, templateWebhook := range templateWebhooks {
 		generateWebhook := &Webhook{
-			RepoID:       generateRepo.ID,
-			URL:          templateWebhook.URL,
-			HTTPMethod:   templateWebhook.HTTPMethod,
-			ContentType:  templateWebhook.ContentType,
-			Secret:       templateWebhook.Secret,
-			HookEvent:    templateWebhook.HookEvent,
-			IsActive:     templateWebhook.IsActive,
-			HookTaskType: templateWebhook.HookTaskType,
-			OrgID:        templateWebhook.OrgID,
-			Events:       templateWebhook.Events,
-			Meta:         templateWebhook.Meta,
+			RepoID:      generateRepo.ID,
+			URL:         templateWebhook.URL,
+			HTTPMethod:  templateWebhook.HTTPMethod,
+			ContentType: templateWebhook.ContentType,
+			Secret:      templateWebhook.Secret,
+			HookEvent:   templateWebhook.HookEvent,
+			IsActive:    templateWebhook.IsActive,
+			Type:        templateWebhook.Type,
+			OrgID:       templateWebhook.OrgID,
+			Events:      templateWebhook.Events,
+			Meta:        templateWebhook.Meta,
 		}
 		if err := createWebhook(ctx.e, generateWebhook); err != nil {
 			return err
diff --git a/models/webhook.go b/models/webhook.go
index 39122808f..dbad2d344 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -110,11 +110,11 @@ type Webhook struct {
 	Secret          string `xorm:"TEXT"`
 	Events          string `xorm:"TEXT"`
 	*HookEvent      `xorm:"-"`
-	IsSSL           bool `xorm:"is_ssl"`
-	IsActive        bool `xorm:"INDEX"`
-	HookTaskType    HookTaskType
-	Meta            string     `xorm:"TEXT"` // store hook-specific attributes
-	LastStatus      HookStatus // Last delivery status
+	IsSSL           bool         `xorm:"is_ssl"`
+	IsActive        bool         `xorm:"INDEX"`
+	Type            HookTaskType `xorm:"char(16) 'type'"`
+	Meta            string       `xorm:"TEXT"` // store hook-specific attributes
+	LastStatus      HookStatus   // Last delivery status
 
 	CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
 	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
diff --git a/modules/convert/convert.go b/modules/convert/convert.go
index 9c90e6ac5..f9f4a641e 100644
--- a/modules/convert/convert.go
+++ b/modules/convert/convert.go
@@ -227,7 +227,7 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook {
 		"url":          w.URL,
 		"content_type": w.ContentType.Name(),
 	}
-	if w.HookTaskType == models.SLACK {
+	if w.Type == models.SLACK {
 		s := webhook.GetSlackHook(w)
 		config["channel"] = s.Channel
 		config["username"] = s.Username
@@ -237,7 +237,7 @@ func ToHook(repoLink string, w *models.Webhook) *api.Hook {
 
 	return &api.Hook{
 		ID:      w.ID,
-		Type:    string(w.HookTaskType),
+		Type:    string(w.Type),
 		URL:     fmt.Sprintf("%s/settings/hooks/%d", repoLink, w.ID),
 		Active:  w.IsActive,
 		Config:  config,
diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go
index 85af6c8e6..c8471184f 100644
--- a/routers/api/v1/utils/hook.go
+++ b/routers/api/v1/utils/hook.go
@@ -132,10 +132,10 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID
 			},
 			BranchFilter: form.BranchFilter,
 		},
-		IsActive:     form.Active,
-		HookTaskType: models.HookTaskType(form.Type),
+		IsActive: form.Active,
+		Type:     models.HookTaskType(form.Type),
 	}
-	if w.HookTaskType == models.SLACK {
+	if w.Type == models.SLACK {
 		channel, ok := form.Config["channel"]
 		if !ok {
 			ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel")
@@ -219,7 +219,7 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *models.Webho
 			w.ContentType = models.ToHookContentType(ct)
 		}
 
-		if w.HookTaskType == models.SLACK {
+		if w.Type == models.SLACK {
 			if channel, ok := form.Config["channel"]; ok {
 				meta, err := json.Marshal(&webhook.SlackMeta{
 					Channel:  channel,
diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go
index 15d2db88c..49aec8184 100644
--- a/routers/repo/webhook.go
+++ b/routers/repo/webhook.go
@@ -208,7 +208,7 @@ func GiteaHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
 		Secret:          form.Secret,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.GITEA,
+		Type:            models.GITEA,
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
 	}
@@ -261,7 +261,7 @@ func newGogsWebhookPost(ctx *context.Context, form auth.NewGogshookForm, kind mo
 		Secret:          form.Secret,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    kind,
+		Type:            kind,
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
 	}
@@ -311,7 +311,7 @@ func DiscordHooksNewPost(ctx *context.Context, form auth.NewDiscordHookForm) {
 		ContentType:     models.ContentTypeJSON,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.DISCORD,
+		Type:            models.DISCORD,
 		Meta:            string(meta),
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -353,7 +353,7 @@ func DingtalkHooksNewPost(ctx *context.Context, form auth.NewDingtalkHookForm) {
 		ContentType:     models.ContentTypeJSON,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.DINGTALK,
+		Type:            models.DINGTALK,
 		Meta:            "",
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -404,7 +404,7 @@ func TelegramHooksNewPost(ctx *context.Context, form auth.NewTelegramHookForm) {
 		ContentType:     models.ContentTypeJSON,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.TELEGRAM,
+		Type:            models.TELEGRAM,
 		Meta:            string(meta),
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -458,7 +458,7 @@ func MatrixHooksNewPost(ctx *context.Context, form auth.NewMatrixHookForm) {
 		HTTPMethod:      "PUT",
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.MATRIX,
+		Type:            models.MATRIX,
 		Meta:            string(meta),
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -500,7 +500,7 @@ func MSTeamsHooksNewPost(ctx *context.Context, form auth.NewMSTeamsHookForm) {
 		ContentType:     models.ContentTypeJSON,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.MSTEAMS,
+		Type:            models.MSTEAMS,
 		Meta:            "",
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -559,7 +559,7 @@ func SlackHooksNewPost(ctx *context.Context, form auth.NewSlackHookForm) {
 		ContentType:     models.ContentTypeJSON,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.SLACK,
+		Type:            models.SLACK,
 		Meta:            string(meta),
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -601,7 +601,7 @@ func FeishuHooksNewPost(ctx *context.Context, form auth.NewFeishuHookForm) {
 		ContentType:     models.ContentTypeJSON,
 		HookEvent:       ParseHookEvent(form.WebhookForm),
 		IsActive:        form.Active,
-		HookTaskType:    models.FEISHU,
+		Type:            models.FEISHU,
 		Meta:            "",
 		OrgID:           orCtx.OrgID,
 		IsSystemWebhook: orCtx.IsSystemWebhook,
@@ -647,8 +647,8 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *models.Webhook) {
 		return nil, nil
 	}
 
-	ctx.Data["HookType"] = w.HookTaskType
-	switch w.HookTaskType {
+	ctx.Data["HookType"] = w.Type
+	switch w.Type {
 	case models.SLACK:
 		ctx.Data["SlackHook"] = webhook.GetSlackHook(w)
 	case models.DISCORD:
diff --git a/services/webhook/webhook.go b/services/webhook/webhook.go
index 104ea3f8b..88dec6bd4 100644
--- a/services/webhook/webhook.go
+++ b/services/webhook/webhook.go
@@ -128,7 +128,7 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
 	// Avoid sending "0 new commits" to non-integration relevant webhooks (e.g. slack, discord, etc.).
 	// Integration webhooks (e.g. drone) still receive the required data.
 	if pushEvent, ok := p.(*api.PushPayload); ok &&
-		w.HookTaskType != models.GITEA && w.HookTaskType != models.GOGS &&
+		w.Type != models.GITEA && w.Type != models.GOGS &&
 		len(pushEvent.Commits) == 0 {
 		return nil
 	}
@@ -144,11 +144,11 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
 
 	var payloader api.Payloader
 	var err error
-	webhook, ok := webhooks[w.HookTaskType]
+	webhook, ok := webhooks[w.Type]
 	if ok {
 		payloader, err = webhook.payloadCreator(p, event, w.Meta)
 		if err != nil {
-			return fmt.Errorf("create payload for %s[%s]: %v", w.HookTaskType, event, err)
+			return fmt.Errorf("create payload for %s[%s]: %v", w.Type, event, err)
 		}
 	} else {
 		p.SetSecret(w.Secret)
@@ -172,7 +172,7 @@ func prepareWebhook(w *models.Webhook, repo *models.Repository, event models.Hoo
 	if err = models.CreateHookTask(&models.HookTask{
 		RepoID:      repo.ID,
 		HookID:      w.ID,
-		Typ:         w.HookTaskType,
+		Typ:         w.Type,
 		URL:         w.URL,
 		Signature:   signature,
 		Payloader:   payloader,