mirror of
https://github.com/songquanpeng/message-pusher.git
synced 2025-09-26 20:21:22 +08:00
feat: webhook server part done (#76)
This commit is contained in:
@@ -113,3 +113,9 @@ const (
|
||||
ChannelStatusEnabled = 1
|
||||
ChannelStatusDisabled = 2
|
||||
)
|
||||
|
||||
const (
|
||||
WebhookStatusUnknown = 0
|
||||
WebhookStatusEnabled = 1
|
||||
WebhookStatusDisabled = 2
|
||||
)
|
||||
|
@@ -116,6 +116,10 @@ func pushMessageHelper(c *gin.Context, message *model.Message) {
|
||||
return
|
||||
}
|
||||
}
|
||||
processMessage(c, message, &user)
|
||||
}
|
||||
|
||||
func processMessage(c *gin.Context, message *model.Message, user *model.User) {
|
||||
if message.Title == "" {
|
||||
message.Title = common.SystemName
|
||||
}
|
||||
@@ -133,7 +137,7 @@ func pushMessageHelper(c *gin.Context, message *model.Message) {
|
||||
})
|
||||
return
|
||||
}
|
||||
err = saveAndSendMessage(&user, message, channel_)
|
||||
err = saveAndSendMessage(user, message, channel_)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
|
256
controller/webhook.go
Normal file
256
controller/webhook.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tidwall/gjson"
|
||||
"message-pusher/common"
|
||||
"message-pusher/model"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetAllWebhooks(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
p, _ := strconv.Atoi(c.Query("p"))
|
||||
if p < 0 {
|
||||
p = 0
|
||||
}
|
||||
webhooks, err := model.GetWebhooksByUserId(userId, p*common.ItemsPerPage, common.ItemsPerPage)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": webhooks,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func SearchWebhooks(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
keyword := c.Query("keyword")
|
||||
webhooks, err := model.SearchWebhooks(userId, keyword)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": webhooks,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func GetWebhook(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
userId := c.GetInt("id")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
webhook_, err := model.GetWebhookById(id, userId)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": webhook_,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func AddWebhook(c *gin.Context) {
|
||||
webhook_ := model.Webhook{}
|
||||
err := c.ShouldBindJSON(&webhook_)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(webhook_.Name) == 0 || len(webhook_.Name) > 20 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "通道名称长度必须在1-20之间",
|
||||
})
|
||||
return
|
||||
}
|
||||
cleanWebhook := model.Webhook{
|
||||
UserId: c.GetInt("id"),
|
||||
Name: webhook_.Name,
|
||||
Status: common.WebhookStatusEnabled,
|
||||
Link: common.GetUUID(),
|
||||
CreatedTime: common.GetTimestamp(),
|
||||
ExtractRule: webhook_.ExtractRule,
|
||||
}
|
||||
err = cleanWebhook.Insert()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func DeleteWebhook(c *gin.Context) {
|
||||
id, _ := strconv.Atoi(c.Param("id"))
|
||||
userId := c.GetInt("id")
|
||||
_, err := model.DeleteWebhookById(id, userId)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func UpdateWebhook(c *gin.Context) {
|
||||
userId := c.GetInt("id")
|
||||
statusOnly := c.Query("status_only")
|
||||
webhook_ := model.Webhook{}
|
||||
err := c.ShouldBindJSON(&webhook_)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
oldWebhook, err := model.GetWebhookById(webhook_.Id, userId)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
cleanWebhook := *oldWebhook
|
||||
if statusOnly != "" {
|
||||
cleanWebhook.Status = webhook_.Status
|
||||
} else {
|
||||
// If you add more fields, please also update webhook_.Update()
|
||||
cleanWebhook.Name = webhook_.Name
|
||||
cleanWebhook.ExtractRule = webhook_.ExtractRule
|
||||
cleanWebhook.ConstructRule = webhook_.ConstructRule
|
||||
cleanWebhook.Channel = webhook_.Channel
|
||||
}
|
||||
err = cleanWebhook.Update()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"message": "",
|
||||
"data": cleanWebhook,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func TriggerWebhook(c *gin.Context) {
|
||||
var reqText string
|
||||
err := c.Bind(&reqText)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
link := c.Param("link")
|
||||
webhook, err := model.GetWebhookByLink(link)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"success": false,
|
||||
"message": "Webhook 不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
if webhook.Status != common.WebhookStatusEnabled {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "Webhook 未启用",
|
||||
})
|
||||
return
|
||||
}
|
||||
user, err := model.GetUserById(webhook.UserId, false)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"success": false,
|
||||
"message": "用户不存在",
|
||||
})
|
||||
return
|
||||
}
|
||||
if user.Status != common.UserStatusEnabled {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "用户已被封禁",
|
||||
})
|
||||
return
|
||||
}
|
||||
extractRule := make(map[string]string)
|
||||
err = json.Unmarshal([]byte(webhook.ExtractRule), &extractRule)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
"message": "Webhook 提取规则解析失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
for key, value := range extractRule {
|
||||
variableValue := gjson.Get(reqText, value).String()
|
||||
webhook.ConstructRule = strings.Replace(webhook.ConstructRule, "$"+key, variableValue, -1)
|
||||
}
|
||||
constructRule := model.WebhookConstructRule{}
|
||||
err = json.Unmarshal([]byte(webhook.ConstructRule), &constructRule)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"message": "Webhook 构建规则解析失败",
|
||||
})
|
||||
return
|
||||
}
|
||||
message := &model.Message{
|
||||
Channel: webhook.Channel,
|
||||
Title: constructRule.Title,
|
||||
Description: constructRule.Description,
|
||||
Content: constructRule.Content,
|
||||
URL: constructRule.URL,
|
||||
}
|
||||
processMessage(c, message, user)
|
||||
}
|
3
go.mod
3
go.mod
@@ -45,6 +45,9 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/tidwall/gjson v1.14.4 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.9 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
|
7
go.sum
7
go.sum
@@ -122,6 +122,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
|
||||
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
|
@@ -68,6 +68,10 @@ func InitDB() (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = db.AutoMigrate(&Webhook{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = createRootAccountIfNeed()
|
||||
return err
|
||||
} else {
|
||||
|
89
model/webhook.go
Normal file
89
model/webhook.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// WebhookConstructRule Keep compatible with Message
|
||||
type WebhookConstructRule struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Content string `json:"content"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Id int `json:"id"`
|
||||
UserId int `json:"user_id" gorm:"index"`
|
||||
Name string `json:"name" gorm:"type:varchar(32);index"`
|
||||
Status int `json:"status" gorm:"default:1"` // enabled, disabled
|
||||
Link string `json:"link" gorm:"type:char(32);uniqueIndex"`
|
||||
CreatedTime int64 `json:"created_time" gorm:"bigint"`
|
||||
ExtractRule string `json:"extract_rule" gorm:"not null"` // how we extract key info from the request
|
||||
ConstructRule string `json:"construct_rule" gorm:"not null"` // how we construct message with the extracted info
|
||||
Channel string `json:"channel" gorm:"type:varchar(32); not null"` // which channel to send our message
|
||||
}
|
||||
|
||||
func GetWebhookById(id int, userId int) (*Webhook, error) {
|
||||
if id == 0 || userId == 0 {
|
||||
return nil, errors.New("id 或 userId 为空!")
|
||||
}
|
||||
c := Webhook{Id: id, UserId: userId}
|
||||
err := DB.Where(c).First(&c).Error
|
||||
return &c, err
|
||||
}
|
||||
|
||||
func GetWebhookByLink(link string) (*Webhook, error) {
|
||||
if link == "" {
|
||||
return nil, errors.New("link 为空!")
|
||||
}
|
||||
c := Webhook{Link: link}
|
||||
err := DB.Where(c).First(&c).Error
|
||||
return &c, err
|
||||
}
|
||||
|
||||
func GetWebhooksByUserId(userId int, startIdx int, num int) (webhooks []*Webhook, err error) {
|
||||
err = DB.Where("user_id = ?", userId).Order("id desc").Limit(num).Offset(startIdx).Find(&webhooks).Error
|
||||
return webhooks, err
|
||||
}
|
||||
|
||||
func SearchWebhooks(userId int, keyword string) (webhooks []*Webhook, err error) {
|
||||
err = DB.Where("user_id = ?", userId).Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&webhooks).Error
|
||||
return webhooks, err
|
||||
}
|
||||
|
||||
func DeleteWebhookById(id int, userId int) (c *Webhook, err error) {
|
||||
// Why we need userId here? In case user want to delete other's c.
|
||||
if id == 0 || userId == 0 {
|
||||
return nil, errors.New("id 或 userId 为空!")
|
||||
}
|
||||
c = &Webhook{Id: id, UserId: userId}
|
||||
err = DB.Where(c).First(&c).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, c.Delete()
|
||||
}
|
||||
|
||||
func (webhook *Webhook) Insert() error {
|
||||
var err error
|
||||
err = DB.Create(webhook).Error
|
||||
return err
|
||||
}
|
||||
|
||||
func (webhook *Webhook) UpdateStatus(status int) error {
|
||||
err := DB.Model(webhook).Update("status", status).Error
|
||||
return err
|
||||
}
|
||||
|
||||
// Update Make sure your token's fields is completed, because this will update non-zero values
|
||||
func (webhook *Webhook) Update() error {
|
||||
var err error
|
||||
err = DB.Model(webhook).Select("name", "extract_rule", "construct_rule", "channel").Updates(webhook).Error
|
||||
return err
|
||||
}
|
||||
|
||||
func (webhook *Webhook) Delete() error {
|
||||
err := DB.Delete(webhook).Error
|
||||
return err
|
||||
}
|
@@ -75,6 +75,16 @@ func SetApiRouter(router *gin.Engine) {
|
||||
channelRoute.PUT("/", controller.UpdateChannel)
|
||||
channelRoute.DELETE("/:id", controller.DeleteChannel)
|
||||
}
|
||||
webhookRoute := apiRouter.Group("/webhook")
|
||||
webhookRoute.Use(middleware.UserAuth())
|
||||
{
|
||||
webhookRoute.GET("/", controller.GetAllWebhooks)
|
||||
webhookRoute.GET("/search", controller.SearchWebhooks)
|
||||
webhookRoute.GET("/:id", controller.GetWebhook)
|
||||
webhookRoute.POST("/", controller.AddWebhook)
|
||||
webhookRoute.PUT("/", controller.UpdateWebhook)
|
||||
webhookRoute.DELETE("/:id", controller.DeleteWebhook)
|
||||
}
|
||||
}
|
||||
pushRouter := router.Group("/push")
|
||||
pushRouter.Use(middleware.GlobalAPIRateLimit())
|
||||
@@ -82,4 +92,9 @@ func SetApiRouter(router *gin.Engine) {
|
||||
pushRouter.GET("/:username", controller.GetPushMessage)
|
||||
pushRouter.POST("/:username", controller.PostPushMessage)
|
||||
}
|
||||
webhookRouter := router.Group("/webhook")
|
||||
webhookRouter.Use(middleware.GlobalAPIRateLimit())
|
||||
{
|
||||
webhookRouter.POST("/:link", controller.TriggerWebhook)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user