#12, add/edit hook
This commit is contained in:
		
							parent
							
								
									9e22840483
								
							
						
					
					
						commit
						9dc3c93a6a
					
				
					 7 changed files with 192 additions and 24 deletions
				
			
		|  | @ -145,6 +145,7 @@ func runWeb(*cli.Context) { | |||
| 	ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true}) | ||||
| 	reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true}) | ||||
| 
 | ||||
| 	bind := binding.Bind | ||||
| 	bindIgnErr := binding.BindIgnErr | ||||
| 
 | ||||
| 	// Routers.
 | ||||
|  | @ -158,6 +159,7 @@ func runWeb(*cli.Context) { | |||
| 	}, reqSignIn) | ||||
| 
 | ||||
| 	// API.
 | ||||
| 	// FIXME: custom form error response.
 | ||||
| 	m.Group("/api", func() { | ||||
| 		m.Group("/v1", func() { | ||||
| 			// Miscellaneous.
 | ||||
|  | @ -170,14 +172,15 @@ func runWeb(*cli.Context) { | |||
| 			}) | ||||
| 
 | ||||
| 			// Repositories.
 | ||||
| 			m.Get("/user/repos", v1.ListMyRepos) | ||||
| 			m.Get("/user/repos", middleware.ApiReqToken(), v1.ListMyRepos) | ||||
| 			m.Group("/repos", func() { | ||||
| 				m.Get("/search", v1.SearchRepos) | ||||
| 				m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.Migrate) | ||||
| 
 | ||||
| 				m.Group("/:username/:reponame", func() { | ||||
| 					m.Combo("/hooks").Get(v1.ListRepoHooks) | ||||
| 				}, middleware.ApiRepoAssignment()) | ||||
| 					m.Combo("/hooks").Get(v1.ListRepoHooks).Post(bind(v1.CreateRepoHookForm{}), v1.CreateRepoHook) | ||||
| 					m.Patch("/hooks/:id:int", bind(v1.EditRepoHookForm{}), v1.EditRepoHook) | ||||
| 				}, middleware.ApiRepoAssignment(), middleware.ApiReqToken()) | ||||
| 			}) | ||||
| 
 | ||||
| 			m.Any("/*", func(ctx *middleware.Context) { | ||||
|  |  | |||
|  | @ -27,6 +27,16 @@ const ( | |||
| 	FORM | ||||
| ) | ||||
| 
 | ||||
| var hookContentTypes = map[string]HookContentType{ | ||||
| 	"json": JSON, | ||||
| 	"form": FORM, | ||||
| } | ||||
| 
 | ||||
| // ToHookContentType returns HookContentType by given name.
 | ||||
| func ToHookContentType(name string) HookContentType { | ||||
| 	return hookContentTypes[name] | ||||
| } | ||||
| 
 | ||||
| func (t HookContentType) Name() string { | ||||
| 	switch t { | ||||
| 	case JSON: | ||||
|  | @ -37,6 +47,12 @@ func (t HookContentType) Name() string { | |||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| // IsValidHookContentType returns true if given name is a valid hook content type.
 | ||||
| func IsValidHookContentType(name string) bool { | ||||
| 	_, ok := hookContentTypes[name] | ||||
| 	return ok | ||||
| } | ||||
| 
 | ||||
| // HookEvent represents events that will delivery hook.
 | ||||
| type HookEvent struct { | ||||
| 	PushOnly bool `json:"push_only"` | ||||
|  | @ -157,6 +173,16 @@ const ( | |||
| 	SLACK | ||||
| ) | ||||
| 
 | ||||
| var hookTaskTypes = map[string]HookTaskType{ | ||||
| 	"gogs":  GOGS, | ||||
| 	"slack": SLACK, | ||||
| } | ||||
| 
 | ||||
| // ToHookTaskType returns HookTaskType by given name.
 | ||||
| func ToHookTaskType(name string) HookTaskType { | ||||
| 	return hookTaskTypes[name] | ||||
| } | ||||
| 
 | ||||
| func (t HookTaskType) Name() string { | ||||
| 	switch t { | ||||
| 	case GOGS: | ||||
|  | @ -167,6 +193,12 @@ func (t HookTaskType) Name() string { | |||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| // IsValidHookTaskType returns true if given name is a valid hook task type.
 | ||||
| func IsValidHookTaskType(name string) bool { | ||||
| 	_, ok := hookTaskTypes[name] | ||||
| 	return ok | ||||
| } | ||||
| 
 | ||||
| type HookEventType string | ||||
| 
 | ||||
| const ( | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ type ( | |||
| 
 | ||||
| 	ApiJsonErr struct { | ||||
| 		Message string `json:"message"` | ||||
| 		DocUrl  string `json:"documentation_url"` | ||||
| 		DocUrl  string `json:"url"` | ||||
| 	} | ||||
| ) | ||||
| 
 | ||||
|  |  | |||
|  | @ -67,3 +67,12 @@ func Toggle(options *ToggleOptions) macaron.Handler { | |||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ApiReqToken() macaron.Handler { | ||||
| 	return func(ctx *Context) { | ||||
| 		if !ctx.IsSigned { | ||||
| 			ctx.Error(403) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -172,13 +172,9 @@ func Migrate(ctx *middleware.Context, form auth.MigrateRepoForm) { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // /user/repos: https://developer.github.com/v3/repos/#list-your-repositories
 | ||||
| // GET /user/repos
 | ||||
| // https://developer.github.com/v3/repos/#list-your-repositories
 | ||||
| func ListMyRepos(ctx *middleware.Context) { | ||||
| 	if !ctx.IsSigned { | ||||
| 		ctx.Error(403) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ownRepos, err := models.GetRepositories(ctx.User.Id, true) | ||||
| 	if err != nil { | ||||
| 		ctx.JSON(500, map[string]interface{}{ | ||||
|  |  | |||
|  | @ -5,24 +5,23 @@ | |||
| package v1 | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 
 | ||||
| 	"github.com/gogits/gogs/models" | ||||
| 	"github.com/gogits/gogs/modules/base" | ||||
| 	"github.com/gogits/gogs/modules/middleware" | ||||
| ) | ||||
| 
 | ||||
| type apiHookConfig struct { | ||||
| 	Url         string `json:"url"` | ||||
| 	ContentType string `json:"content_type"` | ||||
| } | ||||
| 
 | ||||
| type ApiHook struct { | ||||
| 	Id     int64         `json:"id"` | ||||
| 	Type   string        `json:"type"` | ||||
| 	Events []string      `json:"events"` | ||||
| 	Active bool          `json:"active"` | ||||
| 	Config apiHookConfig `json:"config"` | ||||
| 	Id     int64             `json:"id"` | ||||
| 	Type   string            `json:"type"` | ||||
| 	Events []string          `json:"events"` | ||||
| 	Active bool              `json:"active"` | ||||
| 	Config map[string]string `json:"config"` | ||||
| } | ||||
| 
 | ||||
| // /repos/:username/:reponame/hooks: https://developer.github.com/v3/repos/hooks/#list-hooks
 | ||||
| // GET /repos/:username/:reponame/hooks
 | ||||
| // https://developer.github.com/v3/repos/hooks/#list-hooks
 | ||||
| func ListRepoHooks(ctx *middleware.Context) { | ||||
| 	hooks, err := models.GetWebhooksByRepoId(ctx.Repo.Repository.Id) | ||||
| 	if err != nil { | ||||
|  | @ -35,16 +34,144 @@ func ListRepoHooks(ctx *middleware.Context) { | |||
| 
 | ||||
| 	apiHooks := make([]*ApiHook, len(hooks)) | ||||
| 	for i := range hooks { | ||||
| 		apiHooks[i] = &ApiHook{ | ||||
| 		h := &ApiHook{ | ||||
| 			Id:     hooks[i].Id, | ||||
| 			Type:   hooks[i].HookTaskType.Name(), | ||||
| 			Active: hooks[i].IsActive, | ||||
| 			Config: apiHookConfig{hooks[i].Url, hooks[i].ContentType.Name()}, | ||||
| 			Config: make(map[string]string), | ||||
| 		} | ||||
| 
 | ||||
| 		// Currently, onle have push event.
 | ||||
| 		apiHooks[i].Events = []string{"push"} | ||||
| 		h.Events = []string{"push"} | ||||
| 
 | ||||
| 		h.Config["url"] = hooks[i].Url | ||||
| 		h.Config["content_type"] = hooks[i].ContentType.Name() | ||||
| 		if hooks[i].HookTaskType == models.SLACK { | ||||
| 			s := hooks[i].GetSlackHook() | ||||
| 			h.Config["channel"] = s.Channel | ||||
| 		} | ||||
| 
 | ||||
| 		apiHooks[i] = h | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(200, &apiHooks) | ||||
| } | ||||
| 
 | ||||
| type CreateRepoHookForm struct { | ||||
| 	Type   string            `json:"type" binding:"Required"` | ||||
| 	Config map[string]string `json:"config" binding:"Required"` | ||||
| 	Active bool              `json:"active"` | ||||
| } | ||||
| 
 | ||||
| // POST /repos/:username/:reponame/hooks
 | ||||
| // https://developer.github.com/v3/repos/hooks/#create-a-hook
 | ||||
| func CreateRepoHook(ctx *middleware.Context, form CreateRepoHookForm) { | ||||
| 	if !models.IsValidHookTaskType(form.Type) { | ||||
| 		ctx.JSON(422, &base.ApiJsonErr{"invalid hook type", DOC_URL}) | ||||
| 		return | ||||
| 	} | ||||
| 	for _, name := range []string{"url", "content_type"} { | ||||
| 		if _, ok := form.Config[name]; !ok { | ||||
| 			ctx.JSON(422, &base.ApiJsonErr{"missing config option: " + name, DOC_URL}) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| 	if !models.IsValidHookContentType(form.Config["content_type"]) { | ||||
| 		ctx.JSON(422, &base.ApiJsonErr{"invalid content type", DOC_URL}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	w := &models.Webhook{ | ||||
| 		RepoId:      ctx.Repo.Repository.Id, | ||||
| 		Url:         form.Config["url"], | ||||
| 		ContentType: models.ToHookContentType(form.Config["content_type"]), | ||||
| 		Secret:      form.Config["secret"], | ||||
| 		HookEvent: &models.HookEvent{ | ||||
| 			PushOnly: true, // Only support it now.
 | ||||
| 		}, | ||||
| 		IsActive:     form.Active, | ||||
| 		HookTaskType: models.ToHookTaskType(form.Type), | ||||
| 	} | ||||
| 	if w.HookTaskType == models.SLACK { | ||||
| 		channel, ok := form.Config["channel"] | ||||
| 		if !ok { | ||||
| 			ctx.JSON(422, &base.ApiJsonErr{"missing config option: channel", DOC_URL}) | ||||
| 			return | ||||
| 		} | ||||
| 		meta, err := json.Marshal(&models.Slack{ | ||||
| 			Channel: channel, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
| 			ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), DOC_URL}) | ||||
| 			return | ||||
| 		} | ||||
| 		w.Meta = string(meta) | ||||
| 	} | ||||
| 
 | ||||
| 	if err := w.UpdateEvent(); err != nil { | ||||
| 		ctx.JSON(500, &base.ApiJsonErr{"UpdateEvent: " + err.Error(), DOC_URL}) | ||||
| 		return | ||||
| 	} else if err := models.CreateWebhook(w); err != nil { | ||||
| 		ctx.JSON(500, &base.ApiJsonErr{"CreateWebhook: " + err.Error(), DOC_URL}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(201, map[string]interface{}{ | ||||
| 		"ok": true, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| type EditRepoHookForm struct { | ||||
| 	Config map[string]string `json:"config"` | ||||
| 	Active *bool             `json:"active"` | ||||
| } | ||||
| 
 | ||||
| // PATCH /repos/:username/:reponame/hooks/:id
 | ||||
| // https://developer.github.com/v3/repos/hooks/#edit-a-hook
 | ||||
| func EditRepoHook(ctx *middleware.Context, form EditRepoHookForm) { | ||||
| 	w, err := models.GetWebhookById(ctx.ParamsInt64(":id")) | ||||
| 	if err != nil { | ||||
| 		ctx.JSON(500, &base.ApiJsonErr{"GetWebhookById: " + err.Error(), DOC_URL}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if form.Config != nil { | ||||
| 		if url, ok := form.Config["url"]; ok { | ||||
| 			w.Url = url | ||||
| 		} | ||||
| 		if ct, ok := form.Config["content_type"]; ok { | ||||
| 			if !models.IsValidHookContentType(ct) { | ||||
| 				ctx.JSON(422, &base.ApiJsonErr{"invalid content type", DOC_URL}) | ||||
| 				return | ||||
| 			} | ||||
| 			w.ContentType = models.ToHookContentType(ct) | ||||
| 		} | ||||
| 
 | ||||
| 		if w.HookTaskType == models.SLACK { | ||||
| 			if channel, ok := form.Config["channel"]; ok { | ||||
| 				meta, err := json.Marshal(&models.Slack{ | ||||
| 					Channel: channel, | ||||
| 				}) | ||||
| 				if err != nil { | ||||
| 					ctx.JSON(500, &base.ApiJsonErr{"slack: JSON marshal failed: " + err.Error(), DOC_URL}) | ||||
| 					return | ||||
| 				} | ||||
| 				w.Meta = string(meta) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if form.Active != nil { | ||||
| 		w.IsActive = *form.Active | ||||
| 	} | ||||
| 
 | ||||
| 	// FIXME: edit events
 | ||||
| 	if err := models.UpdateWebhook(w); err != nil { | ||||
| 		ctx.JSON(500, &base.ApiJsonErr{"UpdateWebhook: " + err.Error(), DOC_URL}) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.JSON(200, map[string]interface{}{ | ||||
| 		"ok": true, | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -334,6 +334,7 @@ func WebHooksNewPost(ctx *middleware.Context, form auth.NewWebhookForm) { | |||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	// FIXME: code too old here, sync with APIs
 | ||||
| 	ct := models.JSON | ||||
| 	if form.ContentType == "2" { | ||||
| 		ct = models.FORM | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue