diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3509209 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:latest as BUILDER +LABEL maintainer="hourunze" + +# build binary +ARG USER +ARG PASS +RUN echo "machine github.com login $USER password $PASS" >/root/.netrc +RUN mkdir -p /go/src/github.com/opensourceways/message-manager +COPY . /go/src/github.com/opensourceways/message-manager +RUN cd /go/src/github.com/opensourceways/message-manager && CGO_ENABLED=1 go build -v -o ./message-manager main.go + +# copy binary config and utils +FROM openeuler/openeuler:22.03 +RUN dnf -y update && \ + dnf in -y shadow && \ + groupadd -g 1000 message-center && \ + useradd -u 1000 -g message-center -s /bin/bash -m message-center + +USER message-center +COPY --from=BUILDER /go/src/github.com/opensourceways/message-manager /opt/app +WORKDIR /opt/app/ +ENTRYPOINT ["/opt/app/message-manager"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..15e3546 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# OpenEuler-Message-Center-Manager \ No newline at end of file diff --git a/common/cassandra/config.go b/common/cassandra/config.go new file mode 100644 index 0000000..4c3da56 --- /dev/null +++ b/common/cassandra/config.go @@ -0,0 +1,13 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package cassandra + +type Config struct { + Host string `json:"host" required:"true"` + User string `json:"user" required:"true"` + Pwd string `json:"pwd" required:"true"` + Name string `json:"name" required:"true"` + Port int `json:"port" required:"true"` +} diff --git a/common/cassandra/db.go b/common/cassandra/db.go new file mode 100644 index 0000000..cc8889e --- /dev/null +++ b/common/cassandra/db.go @@ -0,0 +1,36 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package cassandra + +import ( + "github.com/gocql/gocql" + "golang.org/x/xerrors" +) + +var ( + session *gocql.Session +) + +func Init(cfg *Config) error { + + cluster := gocql.NewCluster(cfg.Host) + cluster.Keyspace = cfg.Name + cluster.Port = cfg.Port + cluster.Authenticator = gocql.PasswordAuthenticator{ + Username: cfg.User, + Password: cfg.Pwd, + } + sessionInstance, err := cluster.CreateSession() + if err != nil { + return xerrors.Errorf("create session failed, err:%v", err) + } + + session = sessionInstance + return nil +} + +func Session() *gocql.Session { + return session +} diff --git a/common/config/config.go b/common/config/config.go new file mode 100644 index 0000000..1fc1bc7 --- /dev/null +++ b/common/config/config.go @@ -0,0 +1,54 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +// Package config define config interface. +package config + +type configValidate interface { + Validate() error +} + +type configSetDefault interface { + SetDefault() +} + +type configItems interface { + ConfigItems() []interface{} +} + +// SetDefault sets default values for the provided configuration. +func SetDefault(cfg interface{}) { + if f, ok := cfg.(configSetDefault); ok { + f.SetDefault() + } + + if f, ok := cfg.(configItems); ok { + items := f.ConfigItems() + + for i := range items { + SetDefault(items[i]) + } + } +} + +// Validate performs validation for the provided configuration. +func Validate(cfg interface{}) error { + if f, ok := cfg.(configValidate); ok { + if err := f.Validate(); err != nil { + return err + } + } + + if f, ok := cfg.(configItems); ok { + items := f.ConfigItems() + + for i := range items { + if err := Validate(items[i]); err != nil { + return err + } + } + } + + return nil +} diff --git a/common/controller/error.go b/common/controller/error.go new file mode 100644 index 0000000..4401142 --- /dev/null +++ b/common/controller/error.go @@ -0,0 +1,90 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +// Package controller the public ability of controller domain . +package controller + +import ( + "net/http" + + "github.com/opensourceways/message-manager/common/domain/allerror" +) + +const ( + errorSystemError = "system_error" + errorBadRequestBody = "bad_request_body" + errorBadRequestParam = "bad_request_param" + errorUnauthorized = "unauthorized" +) + +type errorCode interface { + ErrorCode() string +} + +type errorNotFound interface { + errorCode + + NotFound() +} + +type errorNoPermission interface { + errorCode + + NoPermission() +} + +func httpError(err error) (int, string) { + if err == nil { + return http.StatusOK, "" + } + + sc := http.StatusInternalServerError + code := errorSystemError + + if v, ok := err.(errorCode); ok { + code = v.ErrorCode() + + if _, ok := err.(errorNotFound); ok { + sc = http.StatusNotFound + + } else if _, ok := err.(errorNoPermission); ok { + sc = http.StatusForbidden + + } else { + switch code { + case allerror.ErrorCodeAccessTokenInvalid: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeSessionIdMissing: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeSessionIdInvalid: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeSessionNotFound: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeSessionInvalid: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeCSRFTokenMissing: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeCSRFTokenInvalid: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeCSRFTokenNotFound: + sc = http.StatusUnauthorized + + case allerror.ErrorCodeAccessDenied: + sc = http.StatusUnauthorized + + default: + sc = http.StatusBadRequest + } + } + } + + return sc, code +} diff --git a/common/controller/error_test.go b/common/controller/error_test.go new file mode 100644 index 0000000..7058739 --- /dev/null +++ b/common/controller/error_test.go @@ -0,0 +1,87 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +// Package controller file scan service. +package controller + +import ( + "fmt" + "net/http" + "testing" + + "github.com/smartystreets/goconvey/convey" + + "github.com/opensourceways/message-manager/common/domain/allerror" +) + +// TestControllerHttpError the unit test for the function +func TestControllerHttpError(t *testing.T) { + convey.Convey("test func httpErro, err nil", t, func() { + errcode, errString := httpError(nil) + convey.So(errcode, convey.ShouldEqual, http.StatusOK) + convey.So(errString, convey.ShouldEqual, "") + }) + + convey.Convey("test func httpErro, err branch test", t, func() { + err := fmt.Errorf("default err") + errcode1, errString1 := httpError(err) + convey.So(errcode1, convey.ShouldEqual, http.StatusInternalServerError) + convey.So(errString1, convey.ShouldEqual, "system_error") + + newErr1 := allerror.NewNotFound(allerror.ErrorCodeModelNotFound, "model not found") + errcode2, errString2 := httpError(newErr1) + convey.So(errcode2, convey.ShouldEqual, http.StatusNotFound) + convey.So(errString2, convey.ShouldEqual, allerror.ErrorCodeModelNotFound) + + newErr2 := allerror.NewNoPermission("no permission") + errcode3, errString3 := httpError(newErr2) + convey.So(errcode3, convey.ShouldEqual, http.StatusForbidden) + convey.So(errString3, convey.ShouldEqual, "no_permission") + + newErr3 := allerror.New(allerror.ErrorCodeAccessTokenInvalid, "error") + errcode4, errString4 := httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeAccessTokenInvalid) + + newErr3 = allerror.New(allerror.ErrorCodeSessionIdMissing, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeSessionIdMissing) + + newErr3 = allerror.New(allerror.ErrorCodeSessionIdInvalid, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeSessionIdInvalid) + + newErr3 = allerror.New(allerror.ErrorCodeSessionNotFound, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeSessionNotFound) + + newErr3 = allerror.New(allerror.ErrorCodeSessionInvalid, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeSessionInvalid) + + newErr3 = allerror.New(allerror.ErrorCodeCSRFTokenMissing, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeCSRFTokenMissing) + + newErr3 = allerror.New(allerror.ErrorCodeCSRFTokenInvalid, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeCSRFTokenInvalid) + + newErr3 = allerror.New(allerror.ErrorCodeCSRFTokenNotFound, "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusUnauthorized) + convey.So(errString4, convey.ShouldEqual, allerror.ErrorCodeCSRFTokenNotFound) + + newErr3 = allerror.New("Default_ERR", "error") + errcode4, errString4 = httpError(newErr3) + convey.So(errcode4, convey.ShouldEqual, http.StatusBadRequest) + convey.So(errString4, convey.ShouldEqual, "Default_ERR") + }) +} diff --git a/common/controller/response.go b/common/controller/response.go new file mode 100644 index 0000000..c76be57 --- /dev/null +++ b/common/controller/response.go @@ -0,0 +1,110 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +// Package controller the common of controller +package controller + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// ResponseData is a struct that holds the response data for an API request. +type ResponseData struct { + Code string `json:"code"` + Msg string `json:"msg"` + Data interface{} `json:"data"` +} + +// newResponseData return the new response data +func newResponseData(data interface{}) ResponseData { + return ResponseData{ + Data: data, + } +} + +// newResponseCodeError return the new response data and code +func newResponseCodeMsg(code, msg string) ResponseData { + return ResponseData{ + Code: code, + Msg: msg, + } +} + +// SendBadRequestBody return the 400 about body invalid +func SendBadRequestBody(ctx *gin.Context, err error) { + if _, ok := err.(errorCode); ok { + SendError(ctx, err) + } else { + _ = ctx.Error(err) + ctx.JSON( + http.StatusBadRequest, + newResponseCodeMsg(errorBadRequestBody, err.Error()), + ) + } +} + +// SendUnauthorized return 401 +func SendUnauthorized(ctx *gin.Context, err error) { + if _, ok := err.(errorCode); ok { + SendError(ctx, err) + } else { + _ = ctx.Error(err) + ctx.JSON( + http.StatusUnauthorized, + newResponseCodeMsg(errorUnauthorized, err.Error()), + ) + } +} + +// SendBadRequestParam return the 400 about param invalid +func SendBadRequestParam(ctx *gin.Context, err error) { + if _, ok := err.(errorCode); ok { + SendError(ctx, err) + } else { + _ = ctx.Error(err) + ctx.JSON( + http.StatusBadRequest, + newResponseCodeMsg(errorBadRequestParam, err.Error()), + ) + } +} + +// SendRespOfPut return the put request +func SendRespOfPut(ctx *gin.Context, data interface{}) { + if data == nil { + ctx.JSON(http.StatusAccepted, newResponseCodeMsg("", "success")) + } else { + ctx.JSON(http.StatusAccepted, newResponseData(data)) + } +} + +// SendRespOfGet return the get request +func SendRespOfGet(ctx *gin.Context, data interface{}) { + ctx.JSON(http.StatusOK, newResponseData(data)) +} + +// SendRespOfPost return the post request +func SendRespOfPost(ctx *gin.Context, data interface{}) { + if data == nil { + ctx.JSON(http.StatusCreated, newResponseCodeMsg("", "success")) + } else { + ctx.JSON(http.StatusCreated, newResponseData(data)) + } +} + +// SendRespOfDelete return the delete request +func SendRespOfDelete(ctx *gin.Context) { + ctx.JSON(http.StatusNoContent, newResponseCodeMsg("", "success")) +} + +// SendError return the 400 about param invalid +func SendError(ctx *gin.Context, err error) { + sc, code := httpError(err) + + _ = ctx.AbortWithError(sc, err) + + ctx.JSON(sc, newResponseCodeMsg(code, err.Error())) +} diff --git a/common/controller/response_test.go b/common/controller/response_test.go new file mode 100644 index 0000000..bdc9fad --- /dev/null +++ b/common/controller/response_test.go @@ -0,0 +1,134 @@ +package controller + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +// 测试 SendBadRequestBody +func TestSendBadRequestBody(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + // 测试常规错误 + err := errors.New("something went wrong") + SendBadRequestBody(c, err) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.JSONEq(t, `{"code":"bad_request_body","msg":"something went wrong","data":null}`, w.Body.String()) +} + +// 测试 SendUnauthorized +func TestSendUnauthorized(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + // 测试常规错误 + err := errors.New("unauthorized access") + SendUnauthorized(c, err) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + assert.JSONEq(t, `{"code":"unauthorized","msg":"unauthorized access","data":null}`, w.Body.String()) +} + +// 测试 SendBadRequestParam +func TestSendBadRequestParam(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + // 测试常规错误 + err := errors.New("parameter invalid") + SendBadRequestParam(c, err) + + assert.Equal(t, http.StatusBadRequest, w.Code) + assert.JSONEq(t, `{"code":"bad_request_param","msg":"parameter invalid","data":null}`, w.Body.String()) +} + +// 测试 SendRespOfPut +func TestSendRespOfPut(t *testing.T) { + gin.SetMode(gin.TestMode) + + // 测试 nil 数据响应 + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + SendRespOfPut(c, nil) + assert.Equal(t, http.StatusAccepted, w.Code) + assert.JSONEq(t, `{"code":"","msg":"success","data":null}`, w.Body.String()) + + // 测试有效数据响应 + w = httptest.NewRecorder() + c, _ = gin.CreateTestContext(w) + + SendRespOfPut(c, "data") + assert.Equal(t, http.StatusAccepted, w.Code) + assert.JSONEq(t, `{"code":"","msg":"","data":"data"}`, w.Body.String()) +} + +// 测试 SendRespOfGet +func TestSendRespOfGet(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + SendRespOfGet(c, "data") + assert.Equal(t, http.StatusOK, w.Code) + assert.JSONEq(t, `{"code":"","msg":"","data":"data"}`, w.Body.String()) +} + +// 测试 SendRespOfPost +func TestSendRespOfPost(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + SendRespOfPost(c, nil) + assert.Equal(t, http.StatusCreated, w.Code) + assert.JSONEq(t, `{"code":"","msg":"success","data":null}`, w.Body.String()) + + w = httptest.NewRecorder() + c, _ = gin.CreateTestContext(w) + + SendRespOfPost(c, "data") + assert.Equal(t, http.StatusCreated, w.Code) + assert.JSONEq(t, `{"code":"","msg":"","data":"data"}`, w.Body.String()) +} + +// 测试 SendRespOfDelete +func TestSendRespOfDelete(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + SendRespOfDelete(c) + assert.Equal(t, http.StatusNoContent, w.Code) +} + +// 测试 SendError +func TestSendError(t *testing.T) { + gin.SetMode(gin.TestMode) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + // 测试常规错误 + err := errors.New("parameter invalid") + SendError(c, err) + + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.JSONEq(t, `{"code":"system_error","msg":"parameter invalid","data":null}`, w.Body.String()) +} diff --git a/common/domain/allerror/error.go b/common/domain/allerror/error.go new file mode 100644 index 0000000..18f589f --- /dev/null +++ b/common/domain/allerror/error.go @@ -0,0 +1,168 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +// Package allerror storage all type of error +package allerror + +import ( + "errors" + "strings" +) + +const ( + // errorCodeNoPermission mean no permission + errorCodeNoPermission = "no_permission" + + ErrorCodeAccessDenied = "unauthorized" + + // ErrorCodeRepoNotFound means repo is not found + ErrorCodeRepoNotFound = "repo_not_found" + + // ErrorCodeAccessTokenInvalid This error code is for restful api + ErrorCodeAccessTokenInvalid = "access_token_invalid" + + // ErrorCodeSessionInvalid is const + ErrorCodeSessionInvalid = "session_invalid" + + // ErrorCodeSessionIdInvalid is const + ErrorCodeSessionIdInvalid = "session_id_invalid" + + // ErrorCodeSessionIdMissing is const + ErrorCodeSessionIdMissing = "session_id_missing" + + // ErrorCodeSessionNotFound is const + ErrorCodeSessionNotFound = "session_not_found" + + // ErrorCodeCSRFTokenMissing is const + ErrorCodeCSRFTokenMissing = "csrf_token_missing" // #nosec G101 + + // ErrorCodeCSRFTokenInvalid is const + ErrorCodeCSRFTokenInvalid = "csrf_token_invalid" // #nosec G101 + + // ErrorCodeCSRFTokenNotFound is const + ErrorCodeCSRFTokenNotFound = "csrf_token_not_found" // #nosec G101 + + // ErrorCodeEmptyRepo means the repo is empty + ErrorCodeEmptyRepo = "empty_repo" + + // ErrorCodeModelNotFound means model is not found + ErrorCodeModelNotFound = "model_not_found" + + // Invalid param + errorCodeInvalidParam = "invalid_param" +) + +// errorImpl +type errorImpl struct { + code string + msg string +} + +// Error return the errorImpl.msg +func (e errorImpl) Error() string { + return e.msg +} + +// ErrorCode return the errorImpl.code +func (e errorImpl) ErrorCode() string { + return e.code +} + +// New the new errorImpl struct +func New(code string, msg string) errorImpl { + v := errorImpl{ + code: code, + } + + if msg == "" { + v.msg = strings.ReplaceAll(code, "_", " ") + } else { + v.msg = msg + } + + return v +} + +// notfoudError not found resource error struct +type notfoudError struct { + errorImpl +} + +// NotFound return empty +func (e notfoudError) NotFound() {} + +// NewNotFound new the not found error +func NewNotFound(code string, msg string) notfoudError { + return notfoudError{errorImpl: New(code, msg)} +} + +// IsNotFound checks if an error is of type "notfoundError" and returns true if it is. +func IsNotFound(err error) bool { + if err == nil { + return false + } + + var notfoudError notfoudError + ok := errors.As(err, ¬foudError) + + return ok +} + +// noPermissionError +type noPermissionError struct { + errorImpl +} + +// NoPermission return empty +func (e noPermissionError) NoPermission() {} + +// NewNoPermission new the no permission error +func NewNoPermission(msg string) noPermissionError { + return noPermissionError{errorImpl: New(errorCodeNoPermission, msg)} +} + +// IsNoPermission check the error is NoPermission +func IsNoPermission(err error) bool { + if err == nil { + return false + } + + var noPermissionError noPermissionError + ok := errors.As(err, &noPermissionError) + + return ok +} + +// NewInvalidParam new the invalid param +func NewInvalidParam(msg string) errorImpl { + return New(errorCodeInvalidParam, msg) +} + +// limitRateError +type limitRateError struct { + errorImpl +} + +// OverLimit is a marker method for over limit rate error. +func (l limitRateError) OverLimit() {} + +// NewOverLimit creates a new over limit error with the specified code and message. +func NewOverLimit(code string, msg string) limitRateError { + return limitRateError{errorImpl: New(code, msg)} +} + +// IsErrorCodeEmptyRepo checks if an error has an error code of ErrorCodeEmptyRepo +func IsErrorCodeEmptyRepo(err error) bool { + if err == nil { + return false + } + + var e errorImpl + ok := errors.As(err, &e) + if !ok { + return false + } + + return e.ErrorCode() == ErrorCodeEmptyRepo +} diff --git a/common/domain/allerror/error_test.go b/common/domain/allerror/error_test.go new file mode 100644 index 0000000..ce58915 --- /dev/null +++ b/common/domain/allerror/error_test.go @@ -0,0 +1,67 @@ +package allerror + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNew(t *testing.T) { + // 测试创建新错误 + err := New("custom_error", "This is a custom error") + assert.Equal(t, "This is a custom error", err.Error()) + assert.Equal(t, "custom_error", err.ErrorCode()) + + // 测试创建新错误时消息为空 + err = New("another_error", "") + assert.Equal(t, "another error", err.Error()) // 下划线替换为空格 +} + +func TestNewNotFound(t *testing.T) { + err := NewNotFound(ErrorCodeRepoNotFound, "Repository not found") + assert.Equal(t, "Repository not found", err.Error()) + assert.Equal(t, ErrorCodeRepoNotFound, err.ErrorCode()) +} + +func TestIsNotFound(t *testing.T) { + err := NewNotFound(ErrorCodeRepoNotFound, "Repository not found") + assert.True(t, IsNotFound(err)) + + normalErr := errors.New("some other error") + assert.False(t, IsNotFound(normalErr)) +} + +func TestNewNoPermission(t *testing.T) { + err := NewNoPermission("Access denied") + assert.Equal(t, "Access denied", err.Error()) + assert.Equal(t, errorCodeNoPermission, err.ErrorCode()) +} + +func TestIsNoPermission(t *testing.T) { + err := NewNoPermission("Access denied") + assert.True(t, IsNoPermission(err)) + + normalErr := errors.New("some other error") + assert.False(t, IsNoPermission(normalErr)) +} + +func TestNewInvalidParam(t *testing.T) { + err := NewInvalidParam("Invalid parameter provided") + assert.Equal(t, "Invalid parameter provided", err.Error()) + assert.Equal(t, errorCodeInvalidParam, err.ErrorCode()) +} + +func TestNewOverLimit(t *testing.T) { + err := NewOverLimit("rate_limit_exceeded", "Rate limit exceeded") + assert.Equal(t, "Rate limit exceeded", err.Error()) + assert.Equal(t, "rate_limit_exceeded", err.ErrorCode()) +} + +func TestIsErrorCodeEmptyRepo(t *testing.T) { + err := New(ErrorCodeEmptyRepo, "The repository is empty") + assert.True(t, IsErrorCodeEmptyRepo(err)) + + err = New("some_other_error", "Some other error occurred") + assert.False(t, IsErrorCodeEmptyRepo(err)) +} diff --git a/common/domain/primitive/config.go b/common/domain/primitive/config.go new file mode 100644 index 0000000..7abf2e3 --- /dev/null +++ b/common/domain/primitive/config.go @@ -0,0 +1,100 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +// Package primitive define the domain of object +package primitive + +import ( + "regexp" + + "k8s.io/apimachinery/pkg/util/sets" +) + +type Config struct { + MSD MSDConfig `json:"msd"` + File FileConfig `json:"file"` + Account AccountConfig `json:"account"` + RandomIdLength int `json:"random_id_length"` +} + +// SetDefault sets default values for Config if they are not provided. +func (cfg *Config) SetDefault() { + if cfg.RandomIdLength <= 0 { + cfg.RandomIdLength = 24 + } +} + +func (cfg *Config) ConfigItems() []interface{} { + return []interface{}{ + &cfg.MSD, + &cfg.File, + &cfg.Account, + } +} + +// MSDConfig represents the configuration for MSD. +type MSDConfig struct { + NameRegexp string `json:"msd_name_regexp" required:"true"` + MinNameLength int `json:"msd_name_min_length" required:"true"` + MaxNameLength int `json:"msd_name_max_length" required:"true"` + + nameRegexp *regexp.Regexp +} + +// Validate validates values for MSDConfig whether they are valid. +func (cfg *MSDConfig) Validate() (err error) { + cfg.nameRegexp, err = regexp.Compile(cfg.NameRegexp) + + return +} + +// FileConfig represents the configuration for file. +type FileConfig struct { + FileRefRegexp string `json:"file_ref_regexp" required:"true"` + FileNameRegexp string `json:"file_name_regexp" required:"true"` + FileRefMinLength int `json:"file_ref_min_length" required:"true"` + FileRefMaxLength int `json:"file_ref_max_length" required:"true"` + FilePathMaxLength int `json:"file_path_max_length" required:"true"` + + fileRefRegexp *regexp.Regexp + fileNameRegexp *regexp.Regexp +} + +// Validate validates values for fileConfig whether they are valid. +func (cfg *FileConfig) Validate() (err error) { + if cfg.fileRefRegexp, err = regexp.Compile(cfg.FileRefRegexp); err != nil { + return err + } + + if cfg.fileNameRegexp, err = regexp.Compile(cfg.FileNameRegexp); err != nil { + return err + } + + return nil +} + +// AccountConfig represents the configuration for Account. +type AccountConfig struct { + NameRegexp string `json:"account_name_regexp" required:"true"` + MinAccountLength int `json:"account_name_min_length" required:"true"` + MaxAccountLength int `json:"account_name_max_length" required:"true"` + ReservedAccounts []string `json:"reserved_accounts" required:"true"` + + nameRegexp *regexp.Regexp + reservedAccounts sets.Set[string] +} + +// Validate validates values for AccountConfig whether they are valid. +func (cfg *AccountConfig) Validate() (err error) { + if cfg.nameRegexp, err = regexp.Compile(cfg.NameRegexp); err != nil { + return err + } + + if len(cfg.ReservedAccounts) > 0 { + cfg.reservedAccounts = sets.New[string]() + cfg.reservedAccounts.Insert(cfg.ReservedAccounts...) + } + + return nil +} diff --git a/common/postgresql/config.go b/common/postgresql/config.go new file mode 100644 index 0000000..64d8724 --- /dev/null +++ b/common/postgresql/config.go @@ -0,0 +1,46 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package postgresql + +import ( + "fmt" + "time" +) + +type Config struct { + Host string `json:"host" required:"true"` + User string `json:"user" required:"true"` + Pwd string `json:"pwd" required:"true"` + Name string `json:"name" required:"true"` + Port int `json:"port" required:"true"` + Life int `json:"life" required:"true"` + MaxConn int `json:"max_conn" required:"true"` + MaxIdle int `json:"max_idle" required:"true"` + Dbcert string `json:"cert"` +} + +func (cfg *Config) SetDefault() { + if cfg.MaxConn <= 0 { + cfg.MaxConn = 500 + } + + if cfg.MaxIdle <= 0 { + cfg.MaxIdle = 250 + } + + if cfg.Life <= 0 { + cfg.Life = 2 + } +} + +func (cfg *Config) getLifeDuration() time.Duration { + return time.Minute * time.Duration(cfg.Life) +} + +func (cfg *Config) dsn() string { + return fmt.Sprintf( + "host=%v user=%v password=%v dbname=%v port=%v sslmode=disable TimeZone=Asia/Shanghai", + cfg.Host, cfg.User, cfg.Pwd, cfg.Name, cfg.Port) +} diff --git a/common/postgresql/config_test.go b/common/postgresql/config_test.go new file mode 100644 index 0000000..76dc6a7 --- /dev/null +++ b/common/postgresql/config_test.go @@ -0,0 +1,24 @@ +package postgresql + +import ( + "testing" + + "github.com/smartystreets/goconvey/convey" +) + +const ( + MaxConn = 500 + MaxIdle = 250 + Life = 2 +) + +// TestSetDefault the unit test for the function +func TestSetDefault(t *testing.T) { + convey.Convey("test SetDefault success", t, func() { + testCfg := &Config{} + testCfg.SetDefault() + convey.So(testCfg.MaxConn, convey.ShouldEqual, MaxConn) + convey.So(testCfg.MaxIdle, convey.ShouldEqual, MaxIdle) + convey.So(testCfg.Life, convey.ShouldEqual, Life) + }) +} diff --git a/common/postgresql/db.go b/common/postgresql/db.go new file mode 100644 index 0000000..cff8946 --- /dev/null +++ b/common/postgresql/db.go @@ -0,0 +1,45 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package postgresql + +import ( + "database/sql" + "errors" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +var ( + sqlDb *sql.DB + db *gorm.DB +) + +func Init(cfg *Config) (err error) { + db, err = gorm.Open( + postgres.New(postgres.Config{ + DSN: cfg.dsn(), + PreferSimpleProtocol: true, + }), + &gorm.Config{}, + ) + if err != nil { + return + } + + if sqlDb, err = db.DB(); err != nil { + return errors.New("db error") + } + + sqlDb.SetConnMaxLifetime(cfg.getLifeDuration()) + sqlDb.SetMaxOpenConns(cfg.MaxConn) + sqlDb.SetMaxIdleConns(cfg.MaxIdle) + + return +} + +func DB() *gorm.DB { + return db +} diff --git a/common/postgresql/db_test.go b/common/postgresql/db_test.go new file mode 100644 index 0000000..22cf88c --- /dev/null +++ b/common/postgresql/db_test.go @@ -0,0 +1,50 @@ +package postgresql + +import ( + "database/sql" + "errors" + "os" + "testing" + + "github.com/agiledragon/gomonkey/v2" + "github.com/smartystreets/goconvey/convey" + "gorm.io/gorm" +) + +func TestInit(t *testing.T) { + mockMethodUseMock := gomonkey.ApplyMethodReturn(&gorm.DB{}, "Use", nil) + defer mockMethodUseMock.Reset() + + convey.Convey("test Init success", t, func() { + mockFunc := gomonkey.ApplyFuncReturn(gorm.Open, &gorm.DB{}, nil) + defer mockFunc.Reset() + + mockFunc2 := gomonkey.ApplyFuncReturn(os.Remove, nil) + defer mockFunc2.Reset() + + mockMethod := gomonkey.ApplyMethodReturn(&gorm.DB{}, "DB", &sql.DB{}, nil) + defer mockMethod.Reset() + + convey.So(Init(&Config{Dbcert: "test"}), convey.ShouldBeNil) + convey.So(DB(), convey.ShouldNotBeNil) + }) + + convey.Convey("test Init failed, open config failed", t, func() { + testErr := errors.New("open config error") + mockFunc := gomonkey.ApplyFuncReturn(gorm.Open, &gorm.DB{}, testErr) + defer mockFunc.Reset() + + convey.So(Init(&Config{}), convey.ShouldResemble, testErr) + }) + + convey.Convey("test Init failed, db error", t, func() { + mockFunc := gomonkey.ApplyFuncReturn(gorm.Open, &gorm.DB{}, nil) + defer mockFunc.Reset() + + testErr := errors.New("db error") + mockMethod := gomonkey.ApplyMethodReturn(&gorm.DB{}, "DB", &sql.DB{}, testErr) + defer mockMethod.Reset() + + convey.So(Init(&Config{}), convey.ShouldResemble, testErr) + }) +} diff --git a/common/user/config.go b/common/user/config.go new file mode 100644 index 0000000..d1d007c --- /dev/null +++ b/common/user/config.go @@ -0,0 +1,23 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package user + +type Config struct { + AuthorHost string `json:"author_host" required:"true"` + EulerCommunity string `json:"euler_community" required:"true"` + EulerAppId string `json:"euler_app_id" required:"true"` + EulerAppSecret string `json:"euler_app_secret" required:"true"` +} + +var config Config + +func Init(cfg *Config) { + config = Config{ + AuthorHost: cfg.AuthorHost, + EulerCommunity: cfg.EulerCommunity, + EulerAppId: cfg.EulerAppId, + EulerAppSecret: cfg.EulerAppSecret, + } +} diff --git a/common/user/user.go b/common/user/user.go new file mode 100644 index 0000000..4e923f6 --- /dev/null +++ b/common/user/user.go @@ -0,0 +1,152 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package user + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "regexp" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "golang.org/x/xerrors" +) + +const OneIdUserCookie = "_Y_G_" + +type ManagerTokenRequest struct { + GrantType string `json:"grant_type"` + AppId string `json:"app_id"` + AppSecret string `json:"app_secret"` +} + +type ManagerTokenResponse struct { + ManagerToken string `json:"token"` +} + +type GetUserInfoResponse struct { + Msg string `json:"msg"` + Code int `json:"code"` + Data `json:"data"` +} + +type Data struct { + UserName string `json:"username"` + Phone string `json:"phone"` + NickName string `json:"nickname"` +} + +func JsonMarshal(t interface{}) ([]byte, error) { + buffer := &bytes.Buffer{} + enc := json.NewEncoder(buffer) + enc.SetEscapeHTML(false) + + if err := enc.Encode(t); err != nil { + return nil, err + } + return buffer.Bytes(), nil +} + +func getManagerToken(appId string, appSecret string) (string, error) { + url := fmt.Sprintf("%s/oneid/manager/token", config.AuthorHost) + reqBody := ManagerTokenRequest{ + GrantType: "token", + AppId: appId, + AppSecret: appSecret, + } + v, err := JsonMarshal(reqBody) + if err != nil { + return "", err + } + + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(v)) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + return + } + }(resp.Body) + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + var data ManagerTokenResponse + if err = json.Unmarshal(body, &data); err != nil { + return "", err + } + return data.ManagerToken, nil +} + +func GetEulerUserName(ctx *gin.Context) (string, error) { + token := ctx.Request.Header.Get("token") + Cookie := ctx.Request.Header.Get("Cookie") + var YGCookie string + re := regexp.MustCompile(`_Y_G_=(.*?)(?:;|$)`) + if re.MatchString(Cookie) { + match := re.FindStringSubmatch(Cookie) + if len(match) > 1 { + YGCookie = match[1] + } + } + + url := fmt.Sprintf("%s/oneid/manager/personal/center/user?community=%s", config.AuthorHost, + config.EulerCommunity) + + managerToken, err := getManagerToken(config.EulerAppId, config.EulerAppSecret) + if err != nil { + logrus.Errorf("get manager token failed, err:%v", err) + return "", err + } + + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + logrus.Errorf("create requeset failed, err:%v", err) + return "", err + } + req.Header.Add("token", managerToken) + req.Header.Add("user-token", token) + req.Header.Add("Cookie", OneIdUserCookie+"="+YGCookie) + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logrus.Errorf("get user name failed, err:%v", err) + return "", err + } + defer func(Body io.ReadCloser) { + err := Body.Close() + if err != nil { + return + } + }(resp.Body) + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + var data GetUserInfoResponse + if err = json.Unmarshal(body, &data); err != nil { + return "", err + } + + if data.UserName == "" { + return "", xerrors.Errorf("the user name is null") + } else { + return data.UserName, nil + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..0eb7f65 --- /dev/null +++ b/config/config.go @@ -0,0 +1,42 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package config + +import ( + "fmt" + "os" + + "sigs.k8s.io/yaml" + + "github.com/opensourceways/message-manager/common/cassandra" + common "github.com/opensourceways/message-manager/common/config" + "github.com/opensourceways/message-manager/common/postgresql" + "github.com/opensourceways/message-manager/common/user" +) + +type Config struct { + Postgresql postgresql.Config `yaml:"postgresql"` + Cassandra cassandra.Config `yaml:"cassandra"` + User user.Config `yaml:"user"` +} + +func LoadFromYaml(path string, cfg interface{}) error { + b, err := os.ReadFile(path) // #nosec G304 + if err != nil { + return err + } + + return yaml.Unmarshal(b, cfg) +} + +func LoadConfig(path string, cfg *Config) error { + if err := LoadFromYaml(path, cfg); err != nil { + return fmt.Errorf("load from yaml failed, %w", err) + } + + common.SetDefault(cfg) + + return common.Validate(cfg) +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..318a1fc --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,74 @@ +// config/config_test.go +package config + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadConfig_Success(t *testing.T) { + // 创建一个临时 YAML 文件 + tempFile, err := os.CreateTemp("", "test_config.yaml") + assert.NoError(t, err) + defer func(name string) { + err := os.Remove(name) + if err != nil { + return + } + }(tempFile.Name()) // 清理临时文件 + + // 写入测试配置 + _, err = tempFile.WriteString(` +postgresql: + user: test_user + pwd: test_password + name: test_db + port: 2345 +cassandra: + host: localhost + port: 1234 +`) + assert.NoError(t, err) + + // 读取配置 + var cfg Config + err = LoadConfig(tempFile.Name(), &cfg) + assert.NoError(t, err) + + // 验证配置内容 + assert.Equal(t, "test_user", cfg.Postgresql.User) + assert.Equal(t, "test_password", cfg.Postgresql.Pwd) + assert.Equal(t, "test_db", cfg.Postgresql.Name) + assert.Equal(t, 2345, cfg.Postgresql.Port) + assert.Equal(t, "localhost", cfg.Cassandra.Host) + assert.Equal(t, 1234, cfg.Cassandra.Port) +} + +func TestLoadConfig_FileNotFound(t *testing.T) { + var cfg Config + err := LoadConfig("non_existent_file.yaml", &cfg) + assert.Error(t, err) // 应该返回错误 +} + +func TestLoadConfig_InvalidYaml(t *testing.T) { + // 创建一个临时 YAML 文件 + tempFile, err := os.CreateTemp("", "invalid_config.yaml") + assert.NoError(t, err) + defer func(name string) { + err := os.Remove(name) + if err != nil { + return + } + }(tempFile.Name()) + + // 写入无效的 YAML 内容 + _, err = tempFile.WriteString(`invalid_yaml: [}`) + assert.NoError(t, err) + + // 尝试加载配置 + var cfg Config + err = LoadConfig(tempFile.Name(), &cfg) + assert.Error(t, err) // 应该返回错误 +} diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..03c4346 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,1431 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": {}, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/message_center/config/push": { + "get": { + "description": "get push config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "GetPushConfig", + "operationId": "getPushConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessagePushDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "put": { + "description": "update a push_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "UpdatePushConfig", + "operationId": "updatePushConfig", + "parameters": [ + { + "description": "updatePushConfigDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updatePushConfigDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add a new push_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "AddPushConfig", + "operationId": "addPushConfig", + "parameters": [ + { + "description": "newPushConfigDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.newPushConfigDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "delete a push_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "RemovePushConfig", + "operationId": "removePushConfig", + "parameters": [ + { + "description": "deletePushConfigDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.deletePushConfigDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/recipient": { + "get": { + "description": "get recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "GetRecipientConfig", + "operationId": "getRecipientConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "integer" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "put": { + "description": "update recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "UpdateRecipientConfig", + "operationId": "updateRecipientConfig", + "parameters": [ + { + "description": "updateRecipientDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updateRecipientDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "AddRecipientConfig", + "operationId": "addRecipientConfig", + "parameters": [ + { + "description": "newRecipientDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.newRecipientDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "remove recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "RemoveRecipientConfig", + "operationId": "removeRecipientConfig", + "parameters": [ + { + "description": "updateRecipientDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updateRecipientDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/recipient/sync": { + "post": { + "description": "sync user info", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "SyncUserInfo", + "operationId": "syncUserInfo", + "parameters": [ + { + "description": "syncUserInfoDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.syncUserInfoDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/subs": { + "get": { + "description": "get subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "GetSubsConfig", + "operationId": "getSubsConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageSubscribeDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "put": { + "description": "update a subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "UpdateSubsConfig", + "operationId": "updateSubsConfig", + "parameters": [ + { + "description": "updateSubscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updateSubscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add a subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "AddSubsConfig", + "operationId": "addSubsConfig", + "parameters": [ + { + "description": "newSubscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.newSubscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "delete a subscribe_config by source and type", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "RemoveSubsConfig", + "operationId": "removeSubsConfig", + "parameters": [ + { + "description": "deleteSubscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.deleteSubscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/subs/all": { + "get": { + "description": "get all subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "GetAllSubsConfig", + "operationId": "getAllSubsConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageSubscribeDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/subs_new": { + "post": { + "description": "save custom filter", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "SaveFilter", + "operationId": "saveFilter", + "parameters": [ + { + "description": "subscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.subscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/inner": { + "put": { + "description": "set message read", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "SetMessageIsRead", + "operationId": "setMessageIsRead", + "parameters": [ + { + "description": "messageStatus", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.messageStatus" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "get inner message", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "GetInnerMessage", + "operationId": "getInnerMessage", + "parameters": [ + { + "type": "string", + "description": "@我的", + "name": "about", + "in": "query" + }, + { + "type": "string", + "description": "eur我执行的", + "name": "build_creator", + "in": "query" + }, + { + "type": "string", + "description": "eur构建环境", + "name": "build_env", + "in": "query" + }, + { + "type": "string", + "description": "eur我的项目", + "name": "build_owner", + "in": "query" + }, + { + "type": "string", + "description": "eur构建状态", + "name": "build_status", + "in": "query" + }, + { + "type": "integer", + "name": "count_per_page", + "in": "query" + }, + { + "type": "string", + "description": "cve影响系统版本", + "name": "cve_affected", + "in": "query" + }, + { + "type": "string", + "description": "cve组件仓库", + "name": "cve_component", + "in": "query" + }, + { + "type": "string", + "description": "cve漏洞状态", + "name": "cve_state", + "in": "query" + }, + { + "type": "string", + "description": "结束时间", + "name": "end_time", + "in": "query" + }, + { + "type": "string", + "description": "事件类型", + "name": "event_type", + "in": "query" + }, + { + "type": "string", + "description": "是否机器人", + "name": "is_bot", + "in": "query" + }, + { + "type": "string", + "description": "是否已读", + "name": "is_read", + "in": "query" + }, + { + "type": "string", + "description": "issue指派者", + "name": "issue_assignee", + "in": "query" + }, + { + "type": "string", + "description": "issue提交者", + "name": "issue_creator", + "in": "query" + }, + { + "type": "string", + "description": "issue事件状态", + "name": "issue_state", + "in": "query" + }, + { + "type": "string", + "description": "关键字模糊搜索", + "name": "key_word", + "in": "query" + }, + { + "type": "string", + "description": "会议操作", + "name": "meeting_action", + "in": "query" + }, + { + "type": "string", + "description": "会议结束时间", + "name": "meeting_end_time", + "in": "query" + }, + { + "type": "string", + "description": "会议所属sig", + "name": "meeting_sig", + "in": "query" + }, + { + "type": "string", + "description": "会议开始时间", + "name": "meeting_start_time", + "in": "query" + }, + { + "type": "string", + "description": "我管理的仓库", + "name": "my_management", + "in": "query" + }, + { + "type": "string", + "description": "我的sig组", + "name": "my_sig", + "in": "query" + }, + { + "type": "string", + "description": "评论类型", + "name": "note_type", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "string", + "description": "pr指派者", + "name": "pr_assignee", + "in": "query" + }, + { + "type": "string", + "description": "pr提交者", + "name": "pr_creator", + "in": "query" + }, + { + "type": "string", + "description": "pr事件状态", + "name": "pr_state", + "in": "query" + }, + { + "type": "string", + "description": "仓库筛选", + "name": "repos", + "in": "query" + }, + { + "type": "string", + "description": "sig组筛选", + "name": "sig", + "in": "query" + }, + { + "type": "string", + "description": "消息源", + "name": "source", + "in": "query" + }, + { + "type": "string", + "description": "起始时间", + "name": "start_time", + "in": "query" + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageListDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "remove message", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "RemoveMessage", + "operationId": "removeMessage", + "parameters": [ + { + "description": "messageStatus", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.messageStatus" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/inner/count": { + "get": { + "description": "get unread inner message count", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "CountAllUnReadMessage", + "operationId": "countAllUnReadMessage", + "responses": { + "202": { + "description": "成功响应", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "未授权", + "schema": { + "type": "string" + } + }, + "500": { + "description": "系统错误", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/inner_quick": { + "get": { + "description": "get inner message by filter", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "GetInnerMessageQuick", + "operationId": "getInnerMessageQuick", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageListDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "app.MessageListDTO": { + "type": "object", + "properties": { + "data_content_type": { + "type": "string" + }, + "data_schema": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "is_read": { + "type": "boolean" + }, + "source": { + "type": "string" + }, + "source_group": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "time": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "app.MessagePushDTO": { + "type": "object", + "properties": { + "need_inner_message": { + "type": "boolean" + }, + "need_mail": { + "type": "boolean" + }, + "need_message": { + "type": "boolean" + }, + "need_phone": { + "type": "boolean" + }, + "recipient_id": { + "type": "integer" + }, + "subscribe_id": { + "type": "integer" + } + } + }, + "app.MessageSubscribeDTO": { + "type": "object", + "properties": { + "event_type": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_default": { + "type": "boolean" + }, + "mode_name": { + "type": "string" + }, + "source": { + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + }, + "controller.deletePushConfigDTO": { + "type": "object", + "properties": { + "recipient_id": { + "type": "integer" + }, + "subscribe_id": { + "type": "integer" + } + } + }, + "controller.deleteSubscribeDTO": { + "type": "object", + "properties": { + "mode_name": { + "type": "string" + }, + "source": { + "type": "string" + } + } + }, + "controller.messageStatus": { + "type": "object", + "properties": { + "event_id": { + "type": "string" + }, + "source": { + "type": "string" + } + } + }, + "controller.newPushConfigDTO": { + "type": "object", + "properties": { + "need_inner_message": { + "type": "boolean" + }, + "need_mail": { + "type": "boolean" + }, + "need_message": { + "type": "boolean" + }, + "need_phone": { + "type": "boolean" + }, + "recipient_id": { + "type": "integer" + }, + "subscribe_id": { + "type": "integer" + } + } + }, + "controller.newRecipientDTO": { + "type": "object", + "properties": { + "mail": { + "type": "string" + }, + "message": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "recipient_id": { + "type": "string" + }, + "remark": { + "type": "string" + } + } + }, + "controller.newSubscribeDTO": { + "type": "object", + "properties": { + "event_type": { + "type": "string" + }, + "mode_name": { + "type": "string" + }, + "source": { + "type": "string" + }, + "spec_version": { + "type": "string" + } + } + }, + "controller.subscribeDTO": { + "type": "object", + "properties": { + "about": { + "description": "@我的", + "type": "string" + }, + "build_creator": { + "description": "eur我执行的", + "type": "string" + }, + "build_env": { + "description": "eur构建环境", + "type": "string" + }, + "build_owner": { + "description": "eur我的项目", + "type": "string" + }, + "build_status": { + "description": "eur构建状态", + "type": "string" + }, + "count_per_page": { + "type": "integer" + }, + "cve_affected": { + "description": "cve影响系统版本", + "type": "string" + }, + "cve_component": { + "description": "cve组件仓库", + "type": "string" + }, + "cve_state": { + "description": "cve漏洞状态", + "type": "string" + }, + "end_time": { + "description": "结束时间", + "type": "string" + }, + "event_type": { + "description": "事件类型", + "type": "string" + }, + "is_bot": { + "description": "是否机器人", + "type": "string" + }, + "is_read": { + "description": "是否已读", + "type": "string" + }, + "issue_assignee": { + "description": "issue指派者", + "type": "string" + }, + "issue_creator": { + "description": "issue提交者", + "type": "string" + }, + "issue_state": { + "description": "issue事件状态", + "type": "string" + }, + "key_word": { + "description": "关键字模糊搜索", + "type": "string" + }, + "meeting_action": { + "description": "会议操作", + "type": "string" + }, + "meeting_end_time": { + "description": "会议结束时间", + "type": "string" + }, + "meeting_sig": { + "description": "会议所属sig", + "type": "string" + }, + "meeting_start_time": { + "description": "会议开始时间", + "type": "string" + }, + "mode_name": { + "type": "string" + }, + "my_management": { + "description": "我管理的仓库", + "type": "string" + }, + "my_sig": { + "description": "我的sig组", + "type": "string" + }, + "note_type": { + "description": "评论类型", + "type": "string" + }, + "page": { + "type": "integer" + }, + "pr_assignee": { + "description": "pr指派者", + "type": "string" + }, + "pr_creator": { + "description": "pr提交者", + "type": "string" + }, + "pr_state": { + "description": "pr事件状态", + "type": "string" + }, + "repos": { + "description": "仓库筛选", + "type": "string" + }, + "sig": { + "description": "sig组筛选", + "type": "string" + }, + "source": { + "description": "消息源", + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "start_time": { + "description": "起始时间", + "type": "string" + } + } + }, + "controller.syncUserInfoDTO": { + "type": "object", + "properties": { + "country_code": { + "type": "string" + }, + "gitee_user_name": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + }, + "controller.updatePushConfigDTO": { + "type": "object", + "properties": { + "need_inner_message": { + "type": "boolean" + }, + "need_mail": { + "type": "boolean" + }, + "need_message": { + "type": "boolean" + }, + "need_phone": { + "type": "boolean" + }, + "recipient_id": { + "type": "string" + }, + "subscribe_id": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "controller.updateRecipientDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "message": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "recipient_id": { + "type": "string" + }, + "remark": { + "type": "string" + } + } + }, + "controller.updateSubscribeDTO": { + "type": "object", + "properties": { + "new_name": { + "type": "string" + }, + "old_name": { + "type": "string" + }, + "source": { + "type": "string" + } + } + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "", + BasePath: "", + Schemes: []string{}, + Title: "Message Manager", + Description: "This is a Message Manager Server.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..fa2352f --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,1405 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is a Message Manager Server.", + "title": "Message Manager", + "contact": {}, + "version": "1.0" + }, + "paths": { + "/message_center/config/push": { + "get": { + "description": "get push config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "GetPushConfig", + "operationId": "getPushConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessagePushDTO" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "put": { + "description": "update a push_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "UpdatePushConfig", + "operationId": "updatePushConfig", + "parameters": [ + { + "description": "updatePushConfigDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updatePushConfigDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add a new push_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "AddPushConfig", + "operationId": "addPushConfig", + "parameters": [ + { + "description": "newPushConfigDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.newPushConfigDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "delete a push_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_push" + ], + "summary": "RemovePushConfig", + "operationId": "removePushConfig", + "parameters": [ + { + "description": "deletePushConfigDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.deletePushConfigDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/recipient": { + "get": { + "description": "get recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "GetRecipientConfig", + "operationId": "getRecipientConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "integer" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "put": { + "description": "update recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "UpdateRecipientConfig", + "operationId": "updateRecipientConfig", + "parameters": [ + { + "description": "updateRecipientDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updateRecipientDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "AddRecipientConfig", + "operationId": "addRecipientConfig", + "parameters": [ + { + "description": "newRecipientDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.newRecipientDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "remove recipient config", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "RemoveRecipientConfig", + "operationId": "removeRecipientConfig", + "parameters": [ + { + "description": "updateRecipientDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updateRecipientDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/recipient/sync": { + "post": { + "description": "sync user info", + "consumes": [ + "application/json" + ], + "tags": [ + "recipient" + ], + "summary": "SyncUserInfo", + "operationId": "syncUserInfo", + "parameters": [ + { + "description": "syncUserInfoDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.syncUserInfoDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/subs": { + "get": { + "description": "get subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "GetSubsConfig", + "operationId": "getSubsConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageSubscribeDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "put": { + "description": "update a subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "UpdateSubsConfig", + "operationId": "updateSubsConfig", + "parameters": [ + { + "description": "updateSubscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.updateSubscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "add a subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "AddSubsConfig", + "operationId": "addSubsConfig", + "parameters": [ + { + "description": "newSubscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.newSubscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "delete a subscribe_config by source and type", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "RemoveSubsConfig", + "operationId": "removeSubsConfig", + "parameters": [ + { + "description": "deleteSubscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.deleteSubscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/subs/all": { + "get": { + "description": "get all subscribe_config", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "GetAllSubsConfig", + "operationId": "getAllSubsConfig", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageSubscribeDTO" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/config/subs_new": { + "post": { + "description": "save custom filter", + "consumes": [ + "application/json" + ], + "tags": [ + "message_subscribe" + ], + "summary": "SaveFilter", + "operationId": "saveFilter", + "parameters": [ + { + "description": "subscribeDTO", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.subscribeDTO" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/inner": { + "put": { + "description": "set message read", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "SetMessageIsRead", + "operationId": "setMessageIsRead", + "parameters": [ + { + "description": "messageStatus", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.messageStatus" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "post": { + "description": "get inner message", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "GetInnerMessage", + "operationId": "getInnerMessage", + "parameters": [ + { + "type": "string", + "description": "@我的", + "name": "about", + "in": "query" + }, + { + "type": "string", + "description": "eur我执行的", + "name": "build_creator", + "in": "query" + }, + { + "type": "string", + "description": "eur构建环境", + "name": "build_env", + "in": "query" + }, + { + "type": "string", + "description": "eur我的项目", + "name": "build_owner", + "in": "query" + }, + { + "type": "string", + "description": "eur构建状态", + "name": "build_status", + "in": "query" + }, + { + "type": "integer", + "name": "count_per_page", + "in": "query" + }, + { + "type": "string", + "description": "cve影响系统版本", + "name": "cve_affected", + "in": "query" + }, + { + "type": "string", + "description": "cve组件仓库", + "name": "cve_component", + "in": "query" + }, + { + "type": "string", + "description": "cve漏洞状态", + "name": "cve_state", + "in": "query" + }, + { + "type": "string", + "description": "结束时间", + "name": "end_time", + "in": "query" + }, + { + "type": "string", + "description": "事件类型", + "name": "event_type", + "in": "query" + }, + { + "type": "string", + "description": "是否机器人", + "name": "is_bot", + "in": "query" + }, + { + "type": "string", + "description": "是否已读", + "name": "is_read", + "in": "query" + }, + { + "type": "string", + "description": "issue指派者", + "name": "issue_assignee", + "in": "query" + }, + { + "type": "string", + "description": "issue提交者", + "name": "issue_creator", + "in": "query" + }, + { + "type": "string", + "description": "issue事件状态", + "name": "issue_state", + "in": "query" + }, + { + "type": "string", + "description": "关键字模糊搜索", + "name": "key_word", + "in": "query" + }, + { + "type": "string", + "description": "会议操作", + "name": "meeting_action", + "in": "query" + }, + { + "type": "string", + "description": "会议结束时间", + "name": "meeting_end_time", + "in": "query" + }, + { + "type": "string", + "description": "会议所属sig", + "name": "meeting_sig", + "in": "query" + }, + { + "type": "string", + "description": "会议开始时间", + "name": "meeting_start_time", + "in": "query" + }, + { + "type": "string", + "description": "我管理的仓库", + "name": "my_management", + "in": "query" + }, + { + "type": "string", + "description": "我的sig组", + "name": "my_sig", + "in": "query" + }, + { + "type": "string", + "description": "评论类型", + "name": "note_type", + "in": "query" + }, + { + "type": "integer", + "name": "page", + "in": "query" + }, + { + "type": "string", + "description": "pr指派者", + "name": "pr_assignee", + "in": "query" + }, + { + "type": "string", + "description": "pr提交者", + "name": "pr_creator", + "in": "query" + }, + { + "type": "string", + "description": "pr事件状态", + "name": "pr_state", + "in": "query" + }, + { + "type": "string", + "description": "仓库筛选", + "name": "repos", + "in": "query" + }, + { + "type": "string", + "description": "sig组筛选", + "name": "sig", + "in": "query" + }, + { + "type": "string", + "description": "消息源", + "name": "source", + "in": "query" + }, + { + "type": "string", + "description": "起始时间", + "name": "start_time", + "in": "query" + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageListDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + }, + "delete": { + "description": "remove message", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "RemoveMessage", + "operationId": "removeMessage", + "parameters": [ + { + "description": "messageStatus", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.messageStatus" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/inner/count": { + "get": { + "description": "get unread inner message count", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "CountAllUnReadMessage", + "operationId": "countAllUnReadMessage", + "responses": { + "202": { + "description": "成功响应", + "schema": { + "type": "object", + "additionalProperties": true + } + }, + "401": { + "description": "未授权", + "schema": { + "type": "string" + } + }, + "500": { + "description": "系统错误", + "schema": { + "type": "string" + } + } + } + } + }, + "/message_center/inner_quick": { + "get": { + "description": "get inner message by filter", + "consumes": [ + "application/json" + ], + "tags": [ + "message_center" + ], + "summary": "GetInnerMessageQuick", + "operationId": "getInnerMessageQuick", + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/app.MessageListDTO" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "string" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "string" + } + } + } + } + } + }, + "definitions": { + "app.MessageListDTO": { + "type": "object", + "properties": { + "data_content_type": { + "type": "string" + }, + "data_schema": { + "type": "string" + }, + "event_id": { + "type": "string" + }, + "is_read": { + "type": "boolean" + }, + "source": { + "type": "string" + }, + "source_group": { + "type": "string" + }, + "source_url": { + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "time": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "type": "string" + } + } + }, + "app.MessagePushDTO": { + "type": "object", + "properties": { + "need_inner_message": { + "type": "boolean" + }, + "need_mail": { + "type": "boolean" + }, + "need_message": { + "type": "boolean" + }, + "need_phone": { + "type": "boolean" + }, + "recipient_id": { + "type": "integer" + }, + "subscribe_id": { + "type": "integer" + } + } + }, + "app.MessageSubscribeDTO": { + "type": "object", + "properties": { + "event_type": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_default": { + "type": "boolean" + }, + "mode_name": { + "type": "string" + }, + "source": { + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + }, + "controller.deletePushConfigDTO": { + "type": "object", + "properties": { + "recipient_id": { + "type": "integer" + }, + "subscribe_id": { + "type": "integer" + } + } + }, + "controller.deleteSubscribeDTO": { + "type": "object", + "properties": { + "mode_name": { + "type": "string" + }, + "source": { + "type": "string" + } + } + }, + "controller.messageStatus": { + "type": "object", + "properties": { + "event_id": { + "type": "string" + }, + "source": { + "type": "string" + } + } + }, + "controller.newPushConfigDTO": { + "type": "object", + "properties": { + "need_inner_message": { + "type": "boolean" + }, + "need_mail": { + "type": "boolean" + }, + "need_message": { + "type": "boolean" + }, + "need_phone": { + "type": "boolean" + }, + "recipient_id": { + "type": "integer" + }, + "subscribe_id": { + "type": "integer" + } + } + }, + "controller.newRecipientDTO": { + "type": "object", + "properties": { + "mail": { + "type": "string" + }, + "message": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "recipient_id": { + "type": "string" + }, + "remark": { + "type": "string" + } + } + }, + "controller.newSubscribeDTO": { + "type": "object", + "properties": { + "event_type": { + "type": "string" + }, + "mode_name": { + "type": "string" + }, + "source": { + "type": "string" + }, + "spec_version": { + "type": "string" + } + } + }, + "controller.subscribeDTO": { + "type": "object", + "properties": { + "about": { + "description": "@我的", + "type": "string" + }, + "build_creator": { + "description": "eur我执行的", + "type": "string" + }, + "build_env": { + "description": "eur构建环境", + "type": "string" + }, + "build_owner": { + "description": "eur我的项目", + "type": "string" + }, + "build_status": { + "description": "eur构建状态", + "type": "string" + }, + "count_per_page": { + "type": "integer" + }, + "cve_affected": { + "description": "cve影响系统版本", + "type": "string" + }, + "cve_component": { + "description": "cve组件仓库", + "type": "string" + }, + "cve_state": { + "description": "cve漏洞状态", + "type": "string" + }, + "end_time": { + "description": "结束时间", + "type": "string" + }, + "event_type": { + "description": "事件类型", + "type": "string" + }, + "is_bot": { + "description": "是否机器人", + "type": "string" + }, + "is_read": { + "description": "是否已读", + "type": "string" + }, + "issue_assignee": { + "description": "issue指派者", + "type": "string" + }, + "issue_creator": { + "description": "issue提交者", + "type": "string" + }, + "issue_state": { + "description": "issue事件状态", + "type": "string" + }, + "key_word": { + "description": "关键字模糊搜索", + "type": "string" + }, + "meeting_action": { + "description": "会议操作", + "type": "string" + }, + "meeting_end_time": { + "description": "会议结束时间", + "type": "string" + }, + "meeting_sig": { + "description": "会议所属sig", + "type": "string" + }, + "meeting_start_time": { + "description": "会议开始时间", + "type": "string" + }, + "mode_name": { + "type": "string" + }, + "my_management": { + "description": "我管理的仓库", + "type": "string" + }, + "my_sig": { + "description": "我的sig组", + "type": "string" + }, + "note_type": { + "description": "评论类型", + "type": "string" + }, + "page": { + "type": "integer" + }, + "pr_assignee": { + "description": "pr指派者", + "type": "string" + }, + "pr_creator": { + "description": "pr提交者", + "type": "string" + }, + "pr_state": { + "description": "pr事件状态", + "type": "string" + }, + "repos": { + "description": "仓库筛选", + "type": "string" + }, + "sig": { + "description": "sig组筛选", + "type": "string" + }, + "source": { + "description": "消息源", + "type": "string" + }, + "spec_version": { + "type": "string" + }, + "start_time": { + "description": "起始时间", + "type": "string" + } + } + }, + "controller.syncUserInfoDTO": { + "type": "object", + "properties": { + "country_code": { + "type": "string" + }, + "gitee_user_name": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + }, + "controller.updatePushConfigDTO": { + "type": "object", + "properties": { + "need_inner_message": { + "type": "boolean" + }, + "need_mail": { + "type": "boolean" + }, + "need_message": { + "type": "boolean" + }, + "need_phone": { + "type": "boolean" + }, + "recipient_id": { + "type": "string" + }, + "subscribe_id": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "controller.updateRecipientDTO": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "mail": { + "type": "string" + }, + "message": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "recipient_id": { + "type": "string" + }, + "remark": { + "type": "string" + } + } + }, + "controller.updateSubscribeDTO": { + "type": "object", + "properties": { + "new_name": { + "type": "string" + }, + "old_name": { + "type": "string" + }, + "source": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..f5a72f6 --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,940 @@ +definitions: + app.MessageListDTO: + properties: + data_content_type: + type: string + data_schema: + type: string + event_id: + type: string + is_read: + type: boolean + source: + type: string + source_group: + type: string + source_url: + type: string + spec_version: + type: string + summary: + type: string + time: + type: string + title: + type: string + type: + type: string + user: + type: string + type: object + app.MessagePushDTO: + properties: + need_inner_message: + type: boolean + need_mail: + type: boolean + need_message: + type: boolean + need_phone: + type: boolean + recipient_id: + type: integer + subscribe_id: + type: integer + type: object + app.MessageSubscribeDTO: + properties: + event_type: + type: string + id: + type: integer + is_default: + type: boolean + mode_name: + type: string + source: + type: string + spec_version: + type: string + user_name: + type: string + type: object + controller.deletePushConfigDTO: + properties: + recipient_id: + type: integer + subscribe_id: + type: integer + type: object + controller.deleteSubscribeDTO: + properties: + mode_name: + type: string + source: + type: string + type: object + controller.messageStatus: + properties: + event_id: + type: string + source: + type: string + type: object + controller.newPushConfigDTO: + properties: + need_inner_message: + type: boolean + need_mail: + type: boolean + need_message: + type: boolean + need_phone: + type: boolean + recipient_id: + type: integer + subscribe_id: + type: integer + type: object + controller.newRecipientDTO: + properties: + mail: + type: string + message: + type: string + phone: + type: string + recipient_id: + type: string + remark: + type: string + type: object + controller.newSubscribeDTO: + properties: + event_type: + type: string + mode_name: + type: string + source: + type: string + spec_version: + type: string + type: object + controller.subscribeDTO: + properties: + about: + description: '@我的' + type: string + build_creator: + description: eur我执行的 + type: string + build_env: + description: eur构建环境 + type: string + build_owner: + description: eur我的项目 + type: string + build_status: + description: eur构建状态 + type: string + count_per_page: + type: integer + cve_affected: + description: cve影响系统版本 + type: string + cve_component: + description: cve组件仓库 + type: string + cve_state: + description: cve漏洞状态 + type: string + end_time: + description: 结束时间 + type: string + event_type: + description: 事件类型 + type: string + is_bot: + description: 是否机器人 + type: string + is_read: + description: 是否已读 + type: string + issue_assignee: + description: issue指派者 + type: string + issue_creator: + description: issue提交者 + type: string + issue_state: + description: issue事件状态 + type: string + key_word: + description: 关键字模糊搜索 + type: string + meeting_action: + description: 会议操作 + type: string + meeting_end_time: + description: 会议结束时间 + type: string + meeting_sig: + description: 会议所属sig + type: string + meeting_start_time: + description: 会议开始时间 + type: string + mode_name: + type: string + my_management: + description: 我管理的仓库 + type: string + my_sig: + description: 我的sig组 + type: string + note_type: + description: 评论类型 + type: string + page: + type: integer + pr_assignee: + description: pr指派者 + type: string + pr_creator: + description: pr提交者 + type: string + pr_state: + description: pr事件状态 + type: string + repos: + description: 仓库筛选 + type: string + sig: + description: sig组筛选 + type: string + source: + description: 消息源 + type: string + spec_version: + type: string + start_time: + description: 起始时间 + type: string + type: object + controller.syncUserInfoDTO: + properties: + country_code: + type: string + gitee_user_name: + type: string + mail: + type: string + phone: + type: string + user_name: + type: string + type: object + controller.updatePushConfigDTO: + properties: + need_inner_message: + type: boolean + need_mail: + type: boolean + need_message: + type: boolean + need_phone: + type: boolean + recipient_id: + type: string + subscribe_id: + items: + type: string + type: array + type: object + controller.updateRecipientDTO: + properties: + id: + type: string + mail: + type: string + message: + type: string + phone: + type: string + recipient_id: + type: string + remark: + type: string + type: object + controller.updateSubscribeDTO: + properties: + new_name: + type: string + old_name: + type: string + source: + type: string + type: object +info: + contact: {} + description: This is a Message Manager Server. + title: Message Manager + version: "1.0" +paths: + /message_center/config/push: + delete: + consumes: + - application/json + description: delete a push_config + operationId: removePushConfig + parameters: + - description: deletePushConfigDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.deletePushConfigDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: RemovePushConfig + tags: + - message_push + get: + consumes: + - application/json + description: get push config + operationId: getPushConfig + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/app.MessagePushDTO' + "500": + description: Internal Server Error + schema: + type: string + summary: GetPushConfig + tags: + - message_push + post: + consumes: + - application/json + description: add a new push_config + operationId: addPushConfig + parameters: + - description: newPushConfigDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.newPushConfigDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: AddPushConfig + tags: + - message_push + put: + consumes: + - application/json + description: update a push_config + operationId: updatePushConfig + parameters: + - description: updatePushConfigDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.updatePushConfigDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: UpdatePushConfig + tags: + - message_push + /message_center/config/recipient: + delete: + consumes: + - application/json + description: remove recipient config + operationId: removeRecipientConfig + parameters: + - description: updateRecipientDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.updateRecipientDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: RemoveRecipientConfig + tags: + - recipient + get: + consumes: + - application/json + description: get recipient config + operationId: getRecipientConfig + responses: + "202": + description: Accepted + schema: + type: integer + "500": + description: Internal Server Error + schema: + type: string + summary: GetRecipientConfig + tags: + - recipient + post: + consumes: + - application/json + description: add recipient config + operationId: addRecipientConfig + parameters: + - description: newRecipientDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.newRecipientDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: AddRecipientConfig + tags: + - recipient + put: + consumes: + - application/json + description: update recipient config + operationId: updateRecipientConfig + parameters: + - description: updateRecipientDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.updateRecipientDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: UpdateRecipientConfig + tags: + - recipient + /message_center/config/recipient/sync: + post: + consumes: + - application/json + description: sync user info + operationId: syncUserInfo + parameters: + - description: syncUserInfoDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.syncUserInfoDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: SyncUserInfo + tags: + - recipient + /message_center/config/subs: + delete: + consumes: + - application/json + description: delete a subscribe_config by source and type + operationId: removeSubsConfig + parameters: + - description: deleteSubscribeDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.deleteSubscribeDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: RemoveSubsConfig + tags: + - message_subscribe + get: + consumes: + - application/json + description: get subscribe_config + operationId: getSubsConfig + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/app.MessageSubscribeDTO' + "401": + description: Unauthorized + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: GetSubsConfig + tags: + - message_subscribe + post: + consumes: + - application/json + description: add a subscribe_config + operationId: addSubsConfig + parameters: + - description: newSubscribeDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.newSubscribeDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: AddSubsConfig + tags: + - message_subscribe + put: + consumes: + - application/json + description: update a subscribe_config + operationId: updateSubsConfig + parameters: + - description: updateSubscribeDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.updateSubscribeDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: UpdateSubsConfig + tags: + - message_subscribe + /message_center/config/subs/all: + get: + consumes: + - application/json + description: get all subscribe_config + operationId: getAllSubsConfig + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/app.MessageSubscribeDTO' + "401": + description: Unauthorized + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: GetAllSubsConfig + tags: + - message_subscribe + /message_center/config/subs_new: + post: + consumes: + - application/json + description: save custom filter + operationId: saveFilter + parameters: + - description: subscribeDTO + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.subscribeDTO' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "401": + description: Unauthorized + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: SaveFilter + tags: + - message_subscribe + /message_center/inner: + delete: + consumes: + - application/json + description: remove message + operationId: removeMessage + parameters: + - description: messageStatus + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.messageStatus' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: RemoveMessage + tags: + - message_center + post: + consumes: + - application/json + description: get inner message + operationId: getInnerMessage + parameters: + - description: '@我的' + in: query + name: about + type: string + - description: eur我执行的 + in: query + name: build_creator + type: string + - description: eur构建环境 + in: query + name: build_env + type: string + - description: eur我的项目 + in: query + name: build_owner + type: string + - description: eur构建状态 + in: query + name: build_status + type: string + - in: query + name: count_per_page + type: integer + - description: cve影响系统版本 + in: query + name: cve_affected + type: string + - description: cve组件仓库 + in: query + name: cve_component + type: string + - description: cve漏洞状态 + in: query + name: cve_state + type: string + - description: 结束时间 + in: query + name: end_time + type: string + - description: 事件类型 + in: query + name: event_type + type: string + - description: 是否机器人 + in: query + name: is_bot + type: string + - description: 是否已读 + in: query + name: is_read + type: string + - description: issue指派者 + in: query + name: issue_assignee + type: string + - description: issue提交者 + in: query + name: issue_creator + type: string + - description: issue事件状态 + in: query + name: issue_state + type: string + - description: 关键字模糊搜索 + in: query + name: key_word + type: string + - description: 会议操作 + in: query + name: meeting_action + type: string + - description: 会议结束时间 + in: query + name: meeting_end_time + type: string + - description: 会议所属sig + in: query + name: meeting_sig + type: string + - description: 会议开始时间 + in: query + name: meeting_start_time + type: string + - description: 我管理的仓库 + in: query + name: my_management + type: string + - description: 我的sig组 + in: query + name: my_sig + type: string + - description: 评论类型 + in: query + name: note_type + type: string + - in: query + name: page + type: integer + - description: pr指派者 + in: query + name: pr_assignee + type: string + - description: pr提交者 + in: query + name: pr_creator + type: string + - description: pr事件状态 + in: query + name: pr_state + type: string + - description: 仓库筛选 + in: query + name: repos + type: string + - description: sig组筛选 + in: query + name: sig + type: string + - description: 消息源 + in: query + name: source + type: string + - description: 起始时间 + in: query + name: start_time + type: string + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/app.MessageListDTO' + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: GetInnerMessage + tags: + - message_center + put: + consumes: + - application/json + description: set message read + operationId: setMessageIsRead + parameters: + - description: messageStatus + in: body + name: body + required: true + schema: + $ref: '#/definitions/controller.messageStatus' + responses: + "202": + description: Accepted + schema: + type: string + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: SetMessageIsRead + tags: + - message_center + /message_center/inner/count: + get: + consumes: + - application/json + description: get unread inner message count + operationId: countAllUnReadMessage + responses: + "202": + description: 成功响应 + schema: + additionalProperties: true + type: object + "401": + description: 未授权 + schema: + type: string + "500": + description: 系统错误 + schema: + type: string + summary: CountAllUnReadMessage + tags: + - message_center + /message_center/inner_quick: + get: + consumes: + - application/json + description: get inner message by filter + operationId: getInnerMessageQuick + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/app.MessageListDTO' + "400": + description: Bad Request + schema: + type: string + "500": + description: Internal Server Error + schema: + type: string + summary: GetInnerMessageQuick + tags: + - message_center +swagger: "2.0" diff --git a/go.mod b/go.mod index cdfa521..7af79fa 100644 --- a/go.mod +++ b/go.mod @@ -1,32 +1,78 @@ -module awesomeProject +module github.com/opensourceways/message-manager go 1.22 -require github.com/gin-gonic/gin v1.9.1 +require ( + github.com/agiledragon/gomonkey/v2 v2.12.0 + github.com/gin-gonic/gin v1.10.0 + github.com/gocql/gocql v1.6.0 + github.com/opensourceways/server-common-lib v0.0.0-20240325033300-a9187b20647e + github.com/sirupsen/logrus v1.9.3 + github.com/smartystreets/goconvey v1.8.1 + github.com/stretchr/testify v1.9.0 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.0 + github.com/swaggo/swag v1.16.3 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 + gorm.io/datatypes v1.2.0 + gorm.io/driver/postgres v1.5.7 + gorm.io/gorm v1.25.10 + k8s.io/apimachinery v0.29.3 + sigs.k8s.io/yaml v1.4.0 +) require ( - github.com/bytedance/sonic v1.9.1 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-playground/validator/v10 v10.22.0 // indirect + github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/snappy v0.0.3 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect + github.com/jtolds/gls v4.20.0+incompatible // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/smarty/assertions v1.15.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.11 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.9.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.24.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.22.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/mysql v1.4.7 // indirect ) diff --git a/go.sum b/go.sum index 1a77fa1..daf0fb5 100644 --- a/go.sum +++ b/go.sum @@ -1,86 +1,227 @@ -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/agiledragon/gomonkey/v2 v2.12.0 h1:ek0dYu9K1rSV+TgkW5LvNNPRWyDZVIxGMCFI6Pz9o38= +github.com/agiledragon/gomonkey/v2 v2.12.0/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= +github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= +github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE= +github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/opensourceways/server-common-lib v0.0.0-20240325033300-a9187b20647e h1:3InsGwv3BwAJ5GbRIbjlsAz4gIJ0aeFeqbY14qeJWnU= +github.com/opensourceways/server-common-lib v0.0.0-20240325033300-a9187b20647e/go.mod h1:p8LVRX70GcSs3hfN4rNRHr6JaYSxKlqi1oos5orO0E0= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= +github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= 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/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.2.0 h1:5YT+eokWdIxhJgWHdrb2zYUimyk0+TaFth+7a0ybzco= +gorm.io/datatypes v1.2.0/go.mod h1:o1dh0ZvjIjhH/bngTpypG6lVRJ5chTBxE09FH/71k04= +gorm.io/driver/mysql v1.4.7 h1:rY46lkCspzGHn7+IYsNpSfEv9tA+SU4SkkB+GFX125Y= +gorm.io/driver/mysql v1.4.7/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc= +gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM= +gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA= +gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0= +gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= +gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +k8s.io/apimachinery v0.29.3 h1:2tbx+5L7RNvqJjn7RIuIKu9XTsIZ9Z5wX2G22XAa5EU= +k8s.io/apimachinery v0.29.3/go.mod h1:hx/S4V2PNW4OMg3WizRrHutyB5la0iCUbZym+W0EQIU= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/main.go b/main.go index e1775d2..43b47fe 100644 --- a/main.go +++ b/main.go @@ -1,21 +1,72 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + package main import ( - "github.com/gin-gonic/gin" - "net/http" + "flag" + "fmt" + "os" + + "github.com/sirupsen/logrus" + + "github.com/opensourceways/message-manager/common/postgresql" + "github.com/opensourceways/message-manager/common/user" + "github.com/opensourceways/message-manager/config" + "github.com/opensourceways/message-manager/server" ) +func gatherOptions(fs *flag.FlagSet, args ...string) (Options, error) { + var o Options + o.AddFlags(fs) + err := fs.Parse(args) + + return o, err +} + +type Options struct { + Config string +} + +func (o *Options) AddFlags(fs *flag.FlagSet) { + fs.StringVar(&o.Config, "config-file", "", "Path to config file.") +} + +// @title Message Manager +// @version 1.0 +// @description This is a Message Manager Server. func main() { - // 创建一个默认的Gin引擎 - router := gin.Default() - - // 定义路由和处理函数 - router.GET("/hello", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "Hello, Message Manager!", - }) - }) - - // 启动HTTP服务器,监听在本地的 8080 端口 - router.Run(":8080") + o, err := gatherOptions( + flag.NewFlagSet(os.Args[0], flag.ExitOnError), + os.Args[1:]..., + ) + if err != nil { + logrus.Fatalf("new Options failed, err:%s", err.Error()) + return + } + + // cfg + cfg := new(config.Config) + + if err = config.LoadConfig(o.Config, cfg); err != nil { + logrus.Errorf("load config, err:%s", err.Error()) + + return + } + + // init postgresql + if err := postgresql.Init(&cfg.Postgresql); err != nil { + fmt.Println("Postgresql数据库初始化失败, err:", err) + return + } + + //if err := cassandra.Init(&cfg.Cassandra); err != nil { + // fmt.Println("Cassandra数据库初始化失败") + //} + + // init user + user.Init(&cfg.User) + + server.StartWebServer() } diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..47e8749 --- /dev/null +++ b/main_test.go @@ -0,0 +1,70 @@ +// main/main_test.go +package main + +import ( + "flag" + "os" + "testing" + + "github.com/opensourceways/message-manager/config" + "github.com/sirupsen/logrus" +) + +func TestGatherOptions(t *testing.T) { + // 测试用例 + tests := []struct { + args []string + expectedFile string + expectError error + }{ + {[]string{"-config-file=config.yaml"}, "config.yaml", nil}, + {[]string{"-invalid-flag"}, "", nil}, + {[]string{}, "", nil}, // 没有提供参数 + } + + for _, test := range tests { + fs := flag.NewFlagSet("test", flag.ContinueOnError) + options, err := gatherOptions(fs, test.args...) + + if nil != test.expectError { + t.Errorf("expected error: %v, got: %v", test.expectError, err) + } + if options.Config != test.expectedFile { + t.Errorf("expected config file: %s, got: %s", test.expectedFile, options.Config) + } + } +} + +func TestLoadConfig(t *testing.T) { + // 创建一个临时配置文件 + tempFile, err := os.CreateTemp("", "config.yaml") + if err != nil { + logrus.Fatalf("failed to create temp file: %v", err) + } + defer func(name string) { + err := os.Remove(name) + if err != nil { + return + } + }(tempFile.Name()) + + _, err = tempFile.WriteString("postgresql:\n host: localhost\n port: 5432\n") + if err != nil { + t.Fatalf("failed to write to temp file: %v", err) + } + err = tempFile.Close() + if err != nil { + return + } + + cfg := new(config.Config) + + err = config.LoadConfig(tempFile.Name(), cfg) + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + + if cfg.Postgresql.Host != "localhost" || cfg.Postgresql.Port != 5432 { + t.Errorf("expected host: localhost, port: 5432, got host: %s, port: %d", cfg.Postgresql.Host, cfg.Postgresql.Port) + } +} diff --git a/message/app/dto.go b/message/app/dto.go new file mode 100644 index 0000000..c979766 --- /dev/null +++ b/message/app/dto.go @@ -0,0 +1,31 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package app + +import ( + "github.com/opensourceways/message-manager/message/domain" +) + +type MessageListDTO = domain.MessageListDO +type MessagePushDTO = domain.MessagePushDO +type MessageRecipientDTO = domain.MessageRecipientDO +type MessageSubscribeDTO = domain.MessageSubscribeDO +type MessageSubscribeDTOWithPushConfig = domain.MessageSubscribeDOWithPushConfig +type CountDTO = domain.CountDO + +type CmdToGetInnerMessageQuick = domain.CmdToGetInnerMessageQuick +type CmdToGetInnerMessage = domain.CmdToGetInnerMessage +type CmdToSetIsRead = domain.CmdToSetIsRead +type CmdToAddPushConfig = domain.CmdToAddPushConfig +type CmdToUpdatePushConfig = domain.CmdToUpdatePushConfig +type CmdToDeletePushConfig = domain.CmdToDeletePushConfig +type CmdToAddRecipient = domain.CmdToAddRecipient +type CmdToUpdateRecipient = domain.CmdToUpdateRecipient +type CmdToDeleteRecipient = domain.CmdToDeleteRecipient +type CmdToSyncUserInfo = domain.CmdToSyncUserInfo +type CmdToGetSubscribe = domain.CmdToGetSubscribe +type CmdToAddSubscribe = domain.CmdToAddSubscribe +type CmdToUpdateSubscribe = domain.CmdToUpdateSubscribe +type CmdToDeleteSubscribe = domain.CmdToDeleteSubscribe diff --git a/message/app/message.go b/message/app/message.go new file mode 100644 index 0000000..1cac5d1 --- /dev/null +++ b/message/app/message.go @@ -0,0 +1,72 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package app + +import ( + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/domain" +) + +type MessageListAppService interface { + GetInnerMessageQuick(userName string, cmd *CmdToGetInnerMessageQuick) ([]MessageListDTO, + int64, error) + GetInnerMessage(userName string, cmd *CmdToGetInnerMessage) ([]MessageListDTO, int64, error) + CountAllUnReadMessage(userName string) ([]CountDTO, error) + SetMessageIsRead(cmd *CmdToSetIsRead) error + RemoveMessage(cmd *CmdToSetIsRead) error +} + +func NewMessageListAppService( + messageListAdapter domain.MessageListAdapter, +) MessageListAppService { + return &messageListAppService{ + messageListAdapter: messageListAdapter, + } +} + +type messageListAppService struct { + messageListAdapter domain.MessageListAdapter +} + +func (s *messageListAppService) GetInnerMessageQuick(userName string, + cmd *CmdToGetInnerMessageQuick) ([]MessageListDTO, int64, error) { + response, count, err := s.messageListAdapter.GetInnerMessageQuick(*cmd, userName) + if err != nil { + return []MessageListDTO{}, 0, err + } + return response, count, nil +} + +func (s *messageListAppService) GetInnerMessage(userName string, + cmd *CmdToGetInnerMessage) ([]MessageListDTO, int64, error) { + response, count, err := s.messageListAdapter.GetInnerMessage(*cmd, userName) + if err != nil { + return []MessageListDTO{}, 0, err + } + return response, count, nil +} + +func (s *messageListAppService) CountAllUnReadMessage(userName string) ([]CountDTO, error) { + count, err := s.messageListAdapter.CountAllUnReadMessage(userName) + if err != nil { + return []CountDTO{}, err + } + return count, nil +} + +func (s *messageListAppService) SetMessageIsRead(cmd *CmdToSetIsRead) error { + if err := s.messageListAdapter.SetMessageIsRead(cmd.Source, cmd.EventId); err != nil { + return xerrors.Errorf("set message is_read failed, err:%v", err.Error()) + } + return nil +} + +func (s *messageListAppService) RemoveMessage(cmd *CmdToSetIsRead) error { + if err := s.messageListAdapter.RemoveMessage(cmd.Source, cmd.EventId); err != nil { + return xerrors.Errorf("set message is_read failed, err:%v", err.Error()) + } + return nil +} diff --git a/message/app/message_test.go b/message/app/message_test.go new file mode 100644 index 0000000..8bf90dd --- /dev/null +++ b/message/app/message_test.go @@ -0,0 +1,231 @@ +package app + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" +) + +// MockMessageListAdapter 是 MessageListAdapter 的模拟实现 +type MockMessageListAdapter struct { + mock.Mock +} + +func (m *MockMessageListAdapter) GetInnerMessageQuick(cmd CmdToGetInnerMessageQuick, userName string) ([]MessageListDTO, int64, error) { + args := m.Called(cmd, userName) + return args.Get(0).([]MessageListDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageListAdapter) GetInnerMessage(cmd CmdToGetInnerMessage, userName string) ([]MessageListDTO, int64, error) { + args := m.Called(cmd, userName) + return args.Get(0).([]MessageListDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageListAdapter) CountAllUnReadMessage(userName string) ([]CountDTO, error) { + args := m.Called(userName) + return args.Get(0).([]CountDTO), args.Error(1) +} + +func (m *MockMessageListAdapter) SetMessageIsRead(source string, eventId string) error { + args := m.Called(source, eventId) + return args.Error(0) +} + +func (m *MockMessageListAdapter) RemoveMessage(source string, eventId string) error { + args := m.Called(source, eventId) + return args.Error(0) +} + +func TestGetInnerMessageQuick(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToGetInnerMessageQuick{Source: "test_source", CountPerPage: 10, PageNum: 1, ModeName: "test_mode"} + mockData := []MessageListDTO{ + { + Title: "Test Title 1", + Summary: "Summary of message 1", + Source: "source1", + Type: "info", + EventId: "event1", + DataContentType: "application/json", + DataSchema: "schema1", + SpecVersion: "1.0", + EventTime: time.Now(), + User: "user1", + SourceUrl: "http://example.com/1", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + IsRead: false, + SourceGroup: "group1", + }, + { + Title: "Test Title 2", + Summary: "Summary of message 2", + Source: "source2", + Type: "alert", + EventId: "event2", + DataContentType: "application/json", + DataSchema: "schema2", + SpecVersion: "1.0", + EventTime: time.Now(), + User: "user2", + SourceUrl: "http://example.com/2", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + IsRead: true, + SourceGroup: "group2", + }, + } + + mockAdapter.On("GetInnerMessageQuick", cmd, userName).Return(mockData, int64(len(mockData)), nil) + + data, count, err := service.GetInnerMessageQuick(userName, &cmd) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + assert.Equal(t, int64(len(mockData)), count) + mockAdapter.AssertExpectations(t) + + cmd1 := CmdToGetInnerMessageQuick{Source: "", CountPerPage: 0, PageNum: 1, + ModeName: "test_mode"} + mockAdapter.On("GetInnerMessageQuick", cmd1, userName).Return([]MessageListDTO{}, + int64(0), xerrors.Errorf("get inner message failed")) + data1, count1, err1 := service.GetInnerMessageQuick(userName, &cmd1) + assert.ErrorContains(t, err1, "get inner message failed") + assert.Equal(t, []MessageListDTO{}, data1) + assert.Equal(t, int64(0), count1) + mockAdapter.AssertExpectations(t) + +} + +func TestGetInnerMessage(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToGetInnerMessage{Source: "test_source", EventType: "test_event", IsRead: "false", CountPerPage: 10, PageNum: 2} + mockData := []MessageListDTO{ + { + Title: "Test Title 1", + Summary: "Summary of message 1", + Source: "source1", + Type: "info", + EventId: "event1", + DataContentType: "application/json", + DataSchema: "schema1", + SpecVersion: "1.0", + EventTime: time.Now(), + User: "user1", + SourceUrl: "http://example.com/1", + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + IsRead: false, + SourceGroup: "group1", + }, + } + + mockAdapter.On("GetInnerMessage", cmd, userName).Return(mockData, int64(len(mockData)), nil) + + data, count, err := service.GetInnerMessage(userName, &cmd) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + assert.Equal(t, int64(len(mockData)), count) + mockAdapter.AssertExpectations(t) + + cmd1 := CmdToGetInnerMessage{Source: "", EventType: "test_event", IsRead: "false", + CountPerPage: 0, PageNum: 2} + mockAdapter.On("GetInnerMessage", cmd1, userName).Return([]MessageListDTO{}, int64(0), + xerrors.Errorf("get inner message failed")) + + data1, count1, err1 := service.GetInnerMessage(userName, &cmd1) + assert.ErrorContains(t, err1, "get inner message failed") + assert.Equal(t, []MessageListDTO{}, data1) + assert.Equal(t, int64(0), count1) + mockAdapter.AssertExpectations(t) +} + +func TestCountAllUnReadMessage(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + userName := "testUser" + mockData := []CountDTO{ + {Source: "source1", Count: 5}, + } + + mockAdapter.On("CountAllUnReadMessage", userName).Return(mockData, nil) + + data, err := service.CountAllUnReadMessage(userName) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + mockAdapter.AssertExpectations(t) + + mockAdapter.On("CountAllUnReadMessage", "").Return([]CountDTO{}, + xerrors.Errorf("get count failed")) + + data1, err1 := service.CountAllUnReadMessage("") + + assert.ErrorContains(t, err1, "get count failed") + assert.Equal(t, []CountDTO{}, data1) + mockAdapter.AssertExpectations(t) + +} + +func TestSetMessageIsRead(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + cmd := CmdToSetIsRead{Source: "test_source", EventId: "event1"} + mockAdapter.On("SetMessageIsRead", cmd.Source, cmd.EventId).Return(nil) + + err := service.SetMessageIsRead(&cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestSetMessageIsRead_Error(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + cmd := CmdToSetIsRead{Source: "test_source", EventId: "event1"} + mockAdapter.On("SetMessageIsRead", cmd.Source, cmd.EventId).Return(xerrors.New("error")) + + err := service.SetMessageIsRead(&cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "set message is_read failed") +} + +func TestRemoveMessage(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + cmd := CmdToSetIsRead{Source: "test_source", EventId: "event1"} + mockAdapter.On("RemoveMessage", cmd.Source, cmd.EventId).Return(nil) + + err := service.RemoveMessage(&cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestRemoveMessage_Error(t *testing.T) { + mockAdapter := new(MockMessageListAdapter) + service := NewMessageListAppService(mockAdapter) + + cmd := CmdToSetIsRead{Source: "test_source", EventId: "event1"} + mockAdapter.On("RemoveMessage", cmd.Source, cmd.EventId).Return(xerrors.New("error")) + + err := service.RemoveMessage(&cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "set message is_read failed") +} diff --git a/message/app/push.go b/message/app/push.go new file mode 100644 index 0000000..e29b1b8 --- /dev/null +++ b/message/app/push.go @@ -0,0 +1,62 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package app + +import ( + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/domain" +) + +type MessagePushAppService interface { + GetPushConfig(countPerPage, pageNum int, userName string, + subsIds []string) ([]MessagePushDTO, error) + AddPushConfig(cmd *CmdToAddPushConfig) error + UpdatePushConfig(cmd *CmdToUpdatePushConfig) error + RemovePushConfig(cmd *CmdToDeletePushConfig) error +} + +func NewMessagePushAppService( + messagePushAdapter domain.MessagePushAdapter, +) MessagePushAppService { + return &messagePushAppService{ + messagePushAdapter: messagePushAdapter, + } +} + +type messagePushAppService struct { + messagePushAdapter domain.MessagePushAdapter +} + +func (s *messagePushAppService) GetPushConfig(countPerPage, pageNum int, userName string, + subsIds []string) ([]MessagePushDTO, error) { + + data, err := s.messagePushAdapter.GetPushConfig(subsIds, countPerPage, pageNum, userName) + if err != nil { + return []MessagePushDTO{}, err + } + return data, nil +} + +func (s *messagePushAppService) AddPushConfig(cmd *CmdToAddPushConfig) error { + if err := s.messagePushAdapter.AddPushConfig(*cmd); err != nil { + return xerrors.Errorf("add message push config failed, err:%v", err.Error()) + } + return nil +} + +func (s *messagePushAppService) UpdatePushConfig(cmd *CmdToUpdatePushConfig) error { + if err := s.messagePushAdapter.UpdatePushConfig(*cmd); err != nil { + return xerrors.Errorf("update message push config failed, err:%v", err.Error()) + } + return nil +} + +func (s *messagePushAppService) RemovePushConfig(cmd *CmdToDeletePushConfig) error { + if err := s.messagePushAdapter.RemovePushConfig(*cmd); err != nil { + return xerrors.Errorf("remove message push config failed, err:%v", err.Error()) + } + return nil +} diff --git a/message/app/push_test.go b/message/app/push_test.go new file mode 100644 index 0000000..9745c92 --- /dev/null +++ b/message/app/push_test.go @@ -0,0 +1,189 @@ +package app + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" +) + +// MockMessagePushAdapter 是 MessagePushAdapter 的模拟实现 +type MockMessagePushAdapter struct { + mock.Mock +} + +func (m *MockMessagePushAdapter) GetPushConfig(subsIds []string, countPerPage, pageNum int, userName string) ([]MessagePushDTO, error) { + args := m.Called(subsIds, countPerPage, pageNum, userName) + if args.Get(0) == nil { + return []MessagePushDTO{}, args.Error(1) + } + return args.Get(0).([]MessagePushDTO), args.Error(1) +} + +func (m *MockMessagePushAdapter) AddPushConfig(cmd CmdToAddPushConfig) error { + args := m.Called(cmd) + return args.Error(0) +} + +func (m *MockMessagePushAdapter) UpdatePushConfig(cmd CmdToUpdatePushConfig) error { + args := m.Called(cmd) + return args.Error(0) +} + +func (m *MockMessagePushAdapter) RemovePushConfig(cmd CmdToDeletePushConfig) error { + args := m.Called(cmd) + return args.Error(0) +} + +func TestGetPushConfig(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + userName := "testUser" + subsIds := []string{"sub1", "sub2"} + countPerPage := 10 + pageNum := 1 + + mockData := []MessagePushDTO{ + { /* 填充必要字段 */ }, + } + + mockAdapter.On("GetPushConfig", subsIds, countPerPage, pageNum, userName).Return(mockData, nil) + + data, err := service.GetPushConfig(countPerPage, pageNum, userName, subsIds) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + mockAdapter.AssertExpectations(t) +} + +func TestGetPushConfig_Error(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + userName := "testUser" + subsIds := []string{"sub1", "sub2"} + countPerPage := 10 + pageNum := 1 + + mockAdapter.On("GetPushConfig", subsIds, countPerPage, pageNum, userName).Return(nil, xerrors.New("error")) + + data, err := service.GetPushConfig(countPerPage, pageNum, userName, subsIds) + + assert.Error(t, err) + assert.Empty(t, data) + mockAdapter.AssertExpectations(t) +} + +func TestAddPushConfig(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + cmd := CmdToAddPushConfig{ + SubscribeId: 1, + RecipientId: 12345, + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: false, + } + mockAdapter.On("AddPushConfig", cmd).Return(nil) + + err := service.AddPushConfig(&cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestAddPushConfig_Error(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + cmd := CmdToAddPushConfig{ + SubscribeId: 1, + RecipientId: 12345, + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: false, + } + mockAdapter.On("AddPushConfig", cmd).Return(xerrors.New("error")) + + err := service.AddPushConfig(&cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "add message push config failed") +} + +func TestUpdatePushConfig(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + cmd := CmdToUpdatePushConfig{ + SubscribeId: []string{"1", "2"}, + RecipientId: "12345", + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: false, + } + mockAdapter.On("UpdatePushConfig", cmd).Return(nil) + + err := service.UpdatePushConfig(&cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestUpdatePushConfig_Error(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + cmd := CmdToUpdatePushConfig{ + SubscribeId: []string{"1", "2"}, + RecipientId: "12345", + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: false, + } + mockAdapter.On("UpdatePushConfig", cmd).Return(xerrors.New("error")) + + err := service.UpdatePushConfig(&cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "update message push config failed") +} + +func TestRemovePushConfig(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + cmd := CmdToDeletePushConfig{ + SubscribeId: 1, + RecipientId: 12345, + } + mockAdapter.On("RemovePushConfig", cmd).Return(nil) + + err := service.RemovePushConfig(&cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestRemovePushConfig_Error(t *testing.T) { + mockAdapter := new(MockMessagePushAdapter) + service := NewMessagePushAppService(mockAdapter) + + cmd := CmdToDeletePushConfig{ + SubscribeId: 1, + RecipientId: 12345, + } + mockAdapter.On("RemovePushConfig", cmd).Return(xerrors.New("error")) + + err := service.RemovePushConfig(&cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "remove message push config failed") +} diff --git a/message/app/recipient.go b/message/app/recipient.go new file mode 100644 index 0000000..9fc513d --- /dev/null +++ b/message/app/recipient.go @@ -0,0 +1,121 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package app + +import ( + "regexp" + + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/domain" +) + +type MessageRecipientAppService interface { + GetRecipientConfig(countPerPage, pageNum int, userName string) ([]MessageRecipientDTO, int64, + error) + AddRecipientConfig(userName string, cmd *CmdToAddRecipient) error + UpdateRecipientConfig(userName string, cmd *CmdToUpdateRecipient) error + RemoveRecipientConfig(userName string, cmd *CmdToDeleteRecipient) error + SyncUserInfo(cmd *CmdToSyncUserInfo) (uint, error) +} + +func NewMessageRecipientAppService( + messageRecipientAdapter domain.MessageRecipientAdapter, +) MessageRecipientAppService { + return &messageRecipientAppService{ + messageRecipientAdapter: messageRecipientAdapter, + } +} + +type messageRecipientAppService struct { + messageRecipientAdapter domain.MessageRecipientAdapter +} + +const ( + EmailRegexp = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + EmailMaxLen = 254 + PhoneRegexp = `^\+861[3-9]\d{9}$` + PhoneLen = 14 +) + +func isValidEmail(email string) bool { + // 简单邮箱正则表达式,可根据需要调整 + emailRegex := regexp.MustCompile(EmailRegexp) + return emailRegex.MatchString(email) && len(email) <= EmailMaxLen +} + +func isValidPhoneNumber(phoneNumber string) bool { + // 中国大陆手机号码的简单正则表达式,可能根据情况调整 + phoneRegex := regexp.MustCompile(PhoneRegexp) + return phoneRegex.MatchString(phoneNumber) && len(phoneNumber) == PhoneLen +} + +func validateData(email string, phoneNumber string) error { + if !isValidEmail(email) { + return xerrors.Errorf("the email is invalid, email:%s", email) + } + if !isValidPhoneNumber(phoneNumber) { + return xerrors.Errorf("the phone number is invalid, phone:%s", phoneNumber) + } + + return nil +} + +func (s *messageRecipientAppService) GetRecipientConfig(countPerPage, pageNum int, userName string) ( + []MessageRecipientDTO, int64, error) { + + data, count, err := s.messageRecipientAdapter.GetRecipientConfig(countPerPage, pageNum, + userName) + if err != nil { + return []MessageRecipientDTO{}, 0, err + } + return data, count, nil +} + +func (s *messageRecipientAppService) AddRecipientConfig(userName string, + cmd *CmdToAddRecipient) error { + if cmd.Name == "" { + return xerrors.Errorf("the recipient is null") + } + + if err := validateData(cmd.Mail, cmd.Phone); err != nil { + return xerrors.Errorf("data is invalid, err:%v", err.Error()) + } + err := s.messageRecipientAdapter.AddRecipientConfig(*cmd, userName) + if err != nil { + return err + } + return nil +} + +func (s *messageRecipientAppService) UpdateRecipientConfig(userName string, + cmd *CmdToUpdateRecipient) error { + if err := validateData(cmd.Mail, cmd.Phone); err != nil { + return xerrors.Errorf("data is invalid, err:%v", err.Error()) + } + + err := s.messageRecipientAdapter.UpdateRecipientConfig(*cmd, userName) + if err != nil { + return err + } + return nil +} + +func (s *messageRecipientAppService) RemoveRecipientConfig(userName string, + cmd *CmdToDeleteRecipient) error { + err := s.messageRecipientAdapter.RemoveRecipientConfig(*cmd, userName) + if err != nil { + return err + } + return nil +} + +func (s *messageRecipientAppService) SyncUserInfo(cmd *CmdToSyncUserInfo) (uint, error) { + data, err := s.messageRecipientAdapter.SyncUserInfo(*cmd) + if err != nil { + return 0, xerrors.Errorf("sync user info failed, err:%v", err) + } + return data, nil +} diff --git a/message/app/recipient_test.go b/message/app/recipient_test.go new file mode 100644 index 0000000..f6678f0 --- /dev/null +++ b/message/app/recipient_test.go @@ -0,0 +1,259 @@ +package app + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" +) + +// MockMessageRecipientAdapter 是 MessageRecipientAdapter 的模拟实现 +type MockMessageRecipientAdapter struct { + mock.Mock +} + +func (m *MockMessageRecipientAdapter) GetRecipientConfig(countPerPage, pageNum int, userName string) ([]MessageRecipientDTO, int64, error) { + args := m.Called(countPerPage, pageNum, userName) + if args.Get(0) == nil { + return []MessageRecipientDTO{}, args.Get(1).(int64), args.Error(2) + } + return args.Get(0).([]MessageRecipientDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageRecipientAdapter) AddRecipientConfig(cmd CmdToAddRecipient, userName string) error { + args := m.Called(cmd, userName) + return args.Error(0) +} + +func (m *MockMessageRecipientAdapter) UpdateRecipientConfig(cmd CmdToUpdateRecipient, userName string) error { + args := m.Called(cmd, userName) + return args.Error(0) +} + +func (m *MockMessageRecipientAdapter) RemoveRecipientConfig(cmd CmdToDeleteRecipient, userName string) error { + args := m.Called(cmd, userName) + return args.Error(0) +} + +func (m *MockMessageRecipientAdapter) SyncUserInfo(cmd CmdToSyncUserInfo) (uint, error) { + args := m.Called(cmd) + return args.Get(0).(uint), args.Error(1) +} + +func TestGetRecipientConfig(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + userName := "testUser" + countPerPage := 10 + pageNum := 1 + + mockData := []MessageRecipientDTO{ + {Id: "1", Name: "Recipient 1", Mail: "recipient1@example.com", Phone: "+8613800138000"}, + } + + mockAdapter.On("GetRecipientConfig", countPerPage, pageNum, userName).Return(mockData, int64(len(mockData)), nil) + + data, count, err := service.GetRecipientConfig(countPerPage, pageNum, userName) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + assert.Equal(t, int64(len(mockData)), count) + mockAdapter.AssertExpectations(t) +} + +func TestGetRecipientConfig_Error(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + userName := "testUser" + countPerPage := 10 + pageNum := 1 + + mockAdapter.On("GetRecipientConfig", countPerPage, pageNum, userName).Return(nil, int64(0), xerrors.New("error")) + + data, count, err := service.GetRecipientConfig(countPerPage, pageNum, userName) + + assert.Error(t, err) + assert.Empty(t, data) + assert.Equal(t, int64(0), count) + mockAdapter.AssertExpectations(t) +} + +func TestAddRecipientConfig(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToAddRecipient{ + Name: "Recipient 1", + Mail: "recipient1@example.com", + Phone: "+8613800138000", + Message: "Hello", + Remark: "Test recipient", + } + userName := "testUser" + + mockAdapter.On("AddRecipientConfig", *cmd, userName).Return(nil) + + err := service.AddRecipientConfig(userName, cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestAddRecipientConfig_Error_NullName(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToAddRecipient{ + Name: "", + Mail: "recipient1@example.com", + Phone: "+8613800138000", + Message: "Hello", + Remark: "Test recipient", + } + userName := "testUser" + + err := service.AddRecipientConfig(userName, cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "the recipient is null") +} + +func TestAddRecipientConfig_Error_InvalidData(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToAddRecipient{ + Name: "Recipient 1", + Mail: "invalid-email", + Phone: "+8613800138000", + Message: "Hello", + Remark: "Test recipient", + } + userName := "testUser" + + err := service.AddRecipientConfig(userName, cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "data is invalid") +} + +func TestUpdateRecipientConfig(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToUpdateRecipient{ + Id: "1", + Name: "Updated Recipient", + Mail: "updated@example.com", + Phone: "+8613800138000", + Message: "Hello", + Remark: "Updated recipient", + } + userName := "testUser" + + mockAdapter.On("UpdateRecipientConfig", *cmd, userName).Return(nil) + + err := service.UpdateRecipientConfig(userName, cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestUpdateRecipientConfig_Error_InvalidData(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToUpdateRecipient{ + Id: "1", + Name: "Updated Recipient", + Mail: "invalid-email", + Phone: "+8613800138000", + Message: "Hello", + Remark: "Updated recipient", + } + userName := "testUser" + + err := service.UpdateRecipientConfig(userName, cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "data is invalid") +} + +func TestRemoveRecipientConfig(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToDeleteRecipient{ + RecipientId: "1", + } + userName := "testUser" + + mockAdapter.On("RemoveRecipientConfig", *cmd, userName).Return(nil) + + err := service.RemoveRecipientConfig(userName, cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestRemoveRecipientConfig_Error(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToDeleteRecipient{ + RecipientId: "1", + } + userName := "testUser" + + mockAdapter.On("RemoveRecipientConfig", *cmd, userName).Return(xerrors.New("error")) + + err := service.RemoveRecipientConfig(userName, cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "error") +} + +func TestSyncUserInfo(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToSyncUserInfo{ + Mail: "user@example.com", + Phone: "+8613800138000", + CountryCode: "86", + UserName: "testUser", + GiteeUserName: "giteeUser", + } + + mockAdapter.On("SyncUserInfo", *cmd).Return(uint(1), nil) + + data, err := service.SyncUserInfo(cmd) + + assert.NoError(t, err) + assert.Equal(t, uint(1), data) + mockAdapter.AssertExpectations(t) +} + +func TestSyncUserInfo_Error(t *testing.T) { + mockAdapter := new(MockMessageRecipientAdapter) + service := NewMessageRecipientAppService(mockAdapter) + + cmd := &CmdToSyncUserInfo{ + Mail: "user@example.com", + Phone: "+8613800138000", + CountryCode: "86", + UserName: "testUser", + GiteeUserName: "giteeUser", + } + + mockAdapter.On("SyncUserInfo", *cmd).Return(uint(0), xerrors.New("error")) + + data, err := service.SyncUserInfo(cmd) + + assert.Error(t, err) + assert.Equal(t, uint(0), data) + mockAdapter.AssertExpectations(t) +} diff --git a/message/app/subscribe.go b/message/app/subscribe.go new file mode 100644 index 0000000..145151f --- /dev/null +++ b/message/app/subscribe.go @@ -0,0 +1,92 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package app + +import ( + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/domain" +) + +type MessageSubscribeAppService interface { + GetAllSubsConfig(userName string) ([]MessageSubscribeDTO, error) + GetSubsConfig(userName string) ([]MessageSubscribeDTOWithPushConfig, int64, error) + SaveFilter(userName string, cmd *CmdToGetSubscribe) error + AddSubsConfig(userName string, cmd *CmdToAddSubscribe) ([]uint, error) + UpdateSubsConfig(userName string, cmd *CmdToUpdateSubscribe) error + RemoveSubsConfig(userName string, cmd *CmdToDeleteSubscribe) error +} + +func NewMessageSubscribeAppService( + messageSubscribeAdapter domain.MessageSubscribeAdapter, +) MessageSubscribeAppService { + return &messageSubscribeAppService{ + messageSubscribeAdapter: messageSubscribeAdapter, + } +} + +type messageSubscribeAppService struct { + messageSubscribeAdapter domain.MessageSubscribeAdapter +} + +func (s *messageSubscribeAppService) GetAllSubsConfig(userName string) ([]MessageSubscribeDTO, error) { + response, err := s.messageSubscribeAdapter.GetAllSubsConfig(userName) + if err != nil { + return []MessageSubscribeDTO{}, err + } + return response, nil +} + +func (s *messageSubscribeAppService) GetSubsConfig(userName string) ([]MessageSubscribeDTOWithPushConfig, + int64, error) { + response, count, err := s.messageSubscribeAdapter.GetSubsConfig(userName) + if err != nil { + return []MessageSubscribeDTOWithPushConfig{}, 0, err + } + return response, count, nil +} + +func (s *messageSubscribeAppService) SaveFilter(userName string, cmd *CmdToGetSubscribe) error { + err := s.messageSubscribeAdapter.SaveFilter(*cmd, userName) + if err != nil { + return err + } + return nil +} + +func (s *messageSubscribeAppService) AddSubsConfig(userName string, + cmd *CmdToAddSubscribe) ([]uint, error) { + + if cmd.ModeName == "" || cmd.ModeFilter == nil || len(cmd.ModeFilter) == 0 { + return []uint{}, xerrors.Errorf("必填项不能为空") + } + + data, err := s.messageSubscribeAdapter.AddSubsConfig(*cmd, userName) + if err != nil { + return []uint{}, xerrors.Errorf("add subs failed, err:%v", err) + } else { + return data, nil + } +} + +func (s *messageSubscribeAppService) UpdateSubsConfig(userName string, + cmd *CmdToUpdateSubscribe) error { + err := s.messageSubscribeAdapter.UpdateSubsConfig(*cmd, userName) + if err != nil { + return xerrors.Errorf("update subs failed, err:%v", err) + } else { + return nil + } +} + +func (s *messageSubscribeAppService) RemoveSubsConfig(userName string, + cmd *CmdToDeleteSubscribe) error { + err := s.messageSubscribeAdapter.RemoveSubsConfig(*cmd, userName) + if err != nil { + return xerrors.Errorf("remove subs failed, err:%v", err) + } else { + return nil + } +} diff --git a/message/app/subscribe_test.go b/message/app/subscribe_test.go new file mode 100644 index 0000000..0c3ca77 --- /dev/null +++ b/message/app/subscribe_test.go @@ -0,0 +1,213 @@ +package app + +import ( + "testing" + + "golang.org/x/xerrors" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "gorm.io/datatypes" +) + +// MockMessageSubscribeAdapter 是 MessageSubscribeAdapter 的模拟实现 +type MockMessageSubscribeAdapter struct { + mock.Mock +} + +func (m *MockMessageSubscribeAdapter) GetAllSubsConfig(userName string) ([]MessageSubscribeDTO, error) { + args := m.Called(userName) + return args.Get(0).([]MessageSubscribeDTO), args.Error(1) +} + +func (m *MockMessageSubscribeAdapter) GetSubsConfig(userName string) ( + []MessageSubscribeDTOWithPushConfig, int64, error) { + args := m.Called(userName) + return args.Get(0).([]MessageSubscribeDTOWithPushConfig), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageSubscribeAdapter) SaveFilter(cmd CmdToGetSubscribe, userName string) error { + args := m.Called(cmd, userName) + return args.Error(0) +} + +func (m *MockMessageSubscribeAdapter) AddSubsConfig(cmd CmdToAddSubscribe, userName string) ([]uint, error) { + args := m.Called(cmd, userName) + return args.Get(0).([]uint), args.Error(1) +} + +func (m *MockMessageSubscribeAdapter) UpdateSubsConfig(cmd CmdToUpdateSubscribe, + userName string) error { + args := m.Called(cmd, userName) + return args.Error(0) +} + +func (m *MockMessageSubscribeAdapter) RemoveSubsConfig(cmd CmdToDeleteSubscribe, userName string) error { + args := m.Called(cmd, userName) + return args.Error(0) +} + +func TestGetAllSubsConfig(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + mockData := []MessageSubscribeDTO{ + {ModeName: "mode1"}, + {ModeName: "mode2"}, + } + + mockAdapter.On("GetAllSubsConfig", userName).Return(mockData, nil) + + data, err := service.GetAllSubsConfig(userName) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + mockAdapter.AssertExpectations(t) + + mockAdapter.On("GetAllSubsConfig", "").Return([]MessageSubscribeDTO{}, + xerrors.Errorf("查询失败")) + + data1, err1 := service.GetAllSubsConfig("") + + assert.ErrorContains(t, err1, "查询失败") + assert.Equal(t, []MessageSubscribeDTO{}, data1) + mockAdapter.AssertExpectations(t) +} + +func TestGetSubsConfig(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + mockData := []MessageSubscribeDTO{ + {ModeName: "mode1"}, + } + mockCount := int64(len(mockData)) + + mockAdapter.On("GetSubsConfig", userName).Return(mockData, mockCount, nil) + + data, count, err := service.GetSubsConfig(userName) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + assert.Equal(t, mockCount, count) + mockAdapter.AssertExpectations(t) + + mockAdapter.On("GetSubsConfig", "").Return([]MessageSubscribeDTO{}, + int64(0), xerrors.Errorf("查询失败")) + + data1, count1, err1 := service.GetSubsConfig("") + + assert.ErrorContains(t, err1, "查询失败") + assert.Equal(t, []MessageSubscribeDTO{}, data1) + assert.Equal(t, int64(0), count1) + mockAdapter.AssertExpectations(t) +} + +func TestSaveFilter(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToGetSubscribe{Source: "source1", EventType: "event_type", IsRead: "false"} + mockAdapter.On("SaveFilter", cmd, userName).Return(nil) + + err := service.SaveFilter(userName, &cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) + + cmd1 := CmdToGetSubscribe{} + mockAdapter.On("SaveFilter", cmd1, "").Return(xerrors.Errorf("用户名为空")) + + err1 := service.SaveFilter("", &cmd1) + + assert.ErrorContains(t, err1, "用户名为空") + mockAdapter.AssertExpectations(t) +} + +func TestAddSubsConfig(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToAddSubscribe{ + Source: "source1", + EventType: "event_type", + SpecVersion: "1.0", + ModeName: "mode1", + ModeFilter: datatypes.JSON(`{"key": "value"}`), + } + mockData := []uint{1, 2, 3} + + mockAdapter.On("AddSubsConfig", cmd, userName).Return(mockData, nil) + + data, err := service.AddSubsConfig(userName, &cmd) + + assert.NoError(t, err) + assert.Equal(t, mockData, data) + mockAdapter.AssertExpectations(t) + + cmd1 := CmdToAddSubscribe{ + Source: "https://gitee.com", + EventType: "note", + SpecVersion: "1.0", + ModeName: "我提的issue的评论", + ModeFilter: datatypes.JSON(`{"NoteEvent.Issue.User.Login": "eq=MaoMao19970922"}`), + } + mockAdapter.On("AddSubsConfig", cmd1, "hourunze97").Return([]uint{}, + xerrors.Errorf("新增配置失败")) + + data1, err1 := service.AddSubsConfig("hourunze97", &cmd1) + assert.ErrorContains(t, err1, "新增配置失败") + assert.Equal(t, []uint{}, data1) + mockAdapter.AssertExpectations(t) +} + +func TestAddSubsConfig_Error(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToAddSubscribe{} // 模式名称和过滤器为空 + + _, err := service.AddSubsConfig(userName, &cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "必填项不能为空") +} + +func TestRemoveSubsConfig(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToDeleteSubscribe{ + Source: "source1", + ModeName: "mode1", + } + mockAdapter.On("RemoveSubsConfig", cmd, userName).Return(nil) + + err := service.RemoveSubsConfig(userName, &cmd) + + assert.NoError(t, err) + mockAdapter.AssertExpectations(t) +} + +func TestRemoveSubsConfig_Error(t *testing.T) { + mockAdapter := new(MockMessageSubscribeAdapter) + service := NewMessageSubscribeAppService(mockAdapter) + + userName := "testUser" + cmd := CmdToDeleteSubscribe{ + Source: "source1", + ModeName: "mode1", + } + mockAdapter.On("RemoveSubsConfig", cmd, userName).Return(xerrors.New("error")) + + err := service.RemoveSubsConfig(userName, &cmd) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "remove subs failed") +} diff --git a/message/controller/message.go b/message/controller/message.go new file mode 100644 index 0000000..95ffc99 --- /dev/null +++ b/message/controller/message.go @@ -0,0 +1,196 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/opensourceways/message-manager/common/user" + "golang.org/x/xerrors" + + commonctl "github.com/opensourceways/message-manager/common/controller" + "github.com/opensourceways/message-manager/message/app" +) + +func AddRouterForMessageListController( + r *gin.Engine, + s app.MessageListAppService, +) { + ctl := messageListController{ + appService: s, + } + + v1 := r.Group("/message_center") + v1.POST("/inner", ctl.GetInnerMessage) + v1.GET("/inner_quick", ctl.GetInnerMessageQuick) + v1.GET("/inner/count", ctl.CountAllUnReadMessage) + v1.PUT("/inner", ctl.SetMessageIsRead) + v1.DELETE("/inner", ctl.RemoveMessage) +} + +type messageListController struct { + appService app.MessageListAppService +} + +// GetInnerMessageQuick +// @Summary GetInnerMessageQuick +// @Description get inner message by filter +// @Tags message_center +// @Accept json +// @Success 202 {object} app.MessageListDTO +// @Failure 500 string system_error 查询失败 +// @Failure 400 string bad_request 请求参数错误 +// @Router /message_center/inner_quick [get] +// @Id getInnerMessageQuick +func (ctl *messageListController) GetInnerMessageQuick(ctx *gin.Context) { + var params queryInnerParamsQuick + if err := ctx.ShouldBindQuery(¶ms); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := params.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if data, count, err := ctl.appService.GetInnerMessageQuick(userName, &cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("查询失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"query_info": data, "count": count}) + } +} + +// GetInnerMessage +// @Summary GetInnerMessage +// @Description get inner message +// @Tags message_center +// @Param Params query queryInnerParams true "Query InnerParams" +// @Accept json +// @Success 202 {object} app.MessageListDTO +// @Failure 500 string system_error 查询失败 +// @Failure 400 string bad_request 无法解析请求正文 +// @Router /message_center/inner [post] +// @Id getInnerMessage +func (ctl *messageListController) GetInnerMessage(ctx *gin.Context) { + var params queryInnerParams + if err := ctx.ShouldBindJSON(¶ms); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := params.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if data, count, err := ctl.appService.GetInnerMessage(userName, &cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("查询失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"query_info": data, "count": count}) + } +} + +// CountAllUnReadMessage +// @Summary CountAllUnReadMessage +// @Description get unread inner message count +// @Tags message_center +// @Accept json +// @Success 202 {object} map[string]interface{} "成功响应" +// @Failure 401 {object} string "未授权" +// @Failure 500 {object} string "系统错误" +// @Router /message_center/inner/count [get] +// @Id countAllUnReadMessage +func (ctl *messageListController) CountAllUnReadMessage(ctx *gin.Context) { + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if data, err := ctl.appService.CountAllUnReadMessage(userName); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("获取失败,"+ + "err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"count": data}) + } +} + +// SetMessageIsRead +// @Summary SetMessageIsRead +// @Description set message read +// @Tags message_center +// @Param body body messageStatus true "messageStatus" +// @Accept json +// @Success 202 string accepted 设置已读成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string system_error 设置已读失败 +// @Router /message_center/inner [put] +// @Id setMessageIsRead +func (ctl *messageListController) SetMessageIsRead(ctx *gin.Context) { + var messages []messageStatus + if err := ctx.BindJSON(&messages); err != nil { + ctx.JSON(http.StatusBadRequest, "无法解析请求正文") + return + } + for _, msg := range messages { + cmd, err := msg.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + if err := ctl.appService.SetMessageIsRead(&cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf( + "设置已读失败,err:%v", err)}) + return + } + } + ctx.JSON(http.StatusAccepted, gin.H{"message": "设置已读成功"}) +} + +// RemoveMessage +// @Summary RemoveMessage +// @Description remove message +// @Tags message_center +// @Param body body messageStatus true "messageStatus" +// @Accept json +// @Success 202 string accepted 消息删除成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string system_error 消息删除失败 +// @Router /message_center/inner [delete] +// @Id removeMessage +func (ctl *messageListController) RemoveMessage(ctx *gin.Context) { + var messages []messageStatus + + if err := ctx.BindJSON(&messages); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("无法解析请求正文")) + return + } + + for _, msg := range messages { + cmd, err := msg.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + if err := ctl.appService.RemoveMessage(&cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("消息删除失败,"+ + "err:%v", err)}) + return + } + } + ctx.JSON(http.StatusAccepted, gin.H{"message": "消息删除成功"}) +} diff --git a/message/controller/message_request.go b/message/controller/message_request.go new file mode 100644 index 0000000..157fa1f --- /dev/null +++ b/message/controller/message_request.go @@ -0,0 +1,105 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import "github.com/opensourceways/message-manager/message/app" + +type queryInnerParams struct { + Source string `json:"source"` // 消息源 + EventType string `json:"event_type"` // 事件类型 + IsRead string `json:"is_read"` // 是否已读 + KeyWord string `json:"key_word"` // 关键字模糊搜索 + IsBot string `json:"is_bot"` // 是否机器人 + GiteeSigs string `json:"sig"` // sig组筛选 + Repos string `json:"repos"` // 仓库筛选 + CountPerPage int `json:"count_per_page"` + PageNum int `json:"page"` + StartTime string `json:"start_time"` // 起始时间 + EndTime string `json:"end_time"` // 结束时间 + MySig string `json:"my_sig"` // 我的sig组 + MyManagement string `json:"my_management"` // 我管理的仓库 + PrState string `json:"pr_state"` // pr事件状态 + PrCreator string `json:"pr_creator"` // pr提交者 + PrAssignee string `json:"pr_assignee"` // pr指派者 + IssueState string `json:"issue_state"` // issue事件状态 + IssueCreator string `json:"issue_creator"` // issue提交者 + IssueAssignee string `json:"issue_assignee"` // issue指派者 + NoteType string `json:"note_type"` // 评论类型 + About string `json:"about"` // @我的 + BuildStatus string `json:"build_status"` // eur构建状态 + BuildOwner string `json:"build_owner"` // eur我的项目 + BuildCreator string `json:"build_creator"` // eur我执行的 + BuildEnv string `json:"build_env"` // eur构建环境 + MeetingAction string `json:"meeting_action"` // 会议操作 + MeetingSigGroup string `json:"meeting_sig"` // 会议所属sig + MeetingStartTime string `json:"meeting_start_time"` // 会议开始时间 + + MeetingEndTime string `json:"meeting_end_time"` // 会议结束时间 + CVEComponent string `json:"cve_component"` // cve组件仓库 + CVEState string `json:"cve_state"` // cve漏洞状态 + CVEAffected string `json:"cve_affected"` // cve影响系统版本 +} + +func (req *queryInnerParams) toCmd() (cmd app.CmdToGetInnerMessage, err error) { + cmd.Source = req.Source + cmd.EventType = req.EventType + cmd.IsRead = req.IsRead + cmd.KeyWord = req.KeyWord + cmd.IsBot = req.IsBot + cmd.GiteeSigs = req.GiteeSigs + cmd.Repos = req.Repos + cmd.CountPerPage = req.CountPerPage + cmd.PageNum = req.PageNum + cmd.StartTime = req.StartTime + cmd.EndTime = req.EndTime + cmd.MySig = req.MySig + cmd.MyManagement = req.MyManagement + cmd.PrState = req.PrState + cmd.PrCreator = req.PrCreator + cmd.PrAssignee = req.PrAssignee + cmd.IssueState = req.IssueState + cmd.IssueCreator = req.IssueCreator + cmd.IssueAssignee = req.IssueAssignee + cmd.NoteType = req.NoteType + cmd.About = req.About + cmd.BuildStatus = req.BuildStatus + cmd.BuildOwner = req.BuildOwner + cmd.BuildCreator = req.BuildCreator + cmd.BuildEnv = req.BuildEnv + cmd.MeetingAction = req.MeetingAction + cmd.MeetingSigGroup = req.MeetingSigGroup + cmd.MeetingStartTime = req.MeetingStartTime + cmd.MeetingEndTime = req.MeetingEndTime + cmd.CVEComponent = req.CVEComponent + cmd.CVEState = req.CVEState + cmd.CVEAffected = req.CVEAffected + return cmd, nil +} + +type queryInnerParamsQuick struct { + Source string `form:"source" json:"source"` + CountPerPage int `form:"count_per_page" json:"count_per_page"` + PageNum int `form:"page" json:"page"` + ModeName string `form:"mode_name" json:"mode_name"` +} + +func (req *queryInnerParamsQuick) toCmd() (cmd app.CmdToGetInnerMessageQuick, err error) { + cmd.Source = req.Source + cmd.CountPerPage = req.CountPerPage + cmd.ModeName = req.ModeName + cmd.PageNum = req.PageNum + return cmd, nil +} + +type messageStatus struct { + Source string `json:"source"` + EventId string `json:"event_id"` +} + +func (req *messageStatus) toCmd() (cmd app.CmdToSetIsRead, err error) { + cmd.EventId = req.EventId + cmd.Source = req.Source + return cmd, nil +} diff --git a/message/controller/message_request_test.go b/message/controller/message_request_test.go new file mode 100644 index 0000000..a077d95 --- /dev/null +++ b/message/controller/message_request_test.go @@ -0,0 +1,80 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestQueryInnerParamsToCmd(t *testing.T) { + req := &queryInnerParams{ + Source: "test_source", + EventType: "test_event", + IsRead: "true", + KeyWord: "test_keyword", + IsBot: "false", + GiteeSigs: "test_sig", + Repos: "test_repo", + CountPerPage: 10, + PageNum: 1, + StartTime: "2024-01-01T00:00:00Z", + EndTime: "2024-01-02T00:00:00Z", + MySig: "my_sig", + MyManagement: "my_management", + PrState: "open", + PrCreator: "creator", + PrAssignee: "assignee", + IssueState: "closed", + IssueCreator: "issue_creator", + IssueAssignee: "issue_assignee", + NoteType: "note", + About: "about", + BuildStatus: "success", + BuildOwner: "owner", + BuildCreator: "builder", + BuildEnv: "production", + MeetingAction: "create", + MeetingSigGroup: "sig_group", + MeetingStartTime: "2024-01-01T09:00:00Z", + MeetingEndTime: "2024-01-01T10:00:00Z", + CVEComponent: "cve_component", + CVEState: "open", + CVEAffected: "yes", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "test_source", cmd.Source) + assert.Equal(t, "test_event", cmd.EventType) + assert.Equal(t, "true", cmd.IsRead) + assert.Equal(t, "test_keyword", cmd.KeyWord) + assert.Equal(t, 10, cmd.CountPerPage) + // 继续验证其他字段... +} + +func TestQueryInnerParamsQuickToCmd(t *testing.T) { + req := &queryInnerParamsQuick{ + Source: "quick_source", + CountPerPage: 5, + PageNum: 2, + ModeName: "quick_mode", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "quick_source", cmd.Source) + assert.Equal(t, 5, cmd.CountPerPage) + assert.Equal(t, "quick_mode", cmd.ModeName) +} + +func TestMessageStatusToCmd(t *testing.T) { + req := &messageStatus{ + Source: "status_source", + EventId: "event_123", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "status_source", cmd.Source) + assert.Equal(t, "event_123", cmd.EventId) +} diff --git a/message/controller/message_test.go b/message/controller/message_test.go new file mode 100644 index 0000000..1de62c4 --- /dev/null +++ b/message/controller/message_test.go @@ -0,0 +1,320 @@ +package controller + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/app" +) + +// MockMessageListAppService 是 MessageListAppService 的模拟实现 +type MockMessageListAppService struct { + mock.Mock +} + +func (m *MockMessageListAppService) GetInnerMessageQuick(userName string, + cmd *app.CmdToGetInnerMessageQuick) ([]app.MessageListDTO, int64, error) { + args := m.Called(userName, cmd) + return args.Get(0).([]app.MessageListDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageListAppService) GetInnerMessage(userName string, + cmd *app.CmdToGetInnerMessage) ([]app.MessageListDTO, int64, error) { + args := m.Called(userName, cmd) + return args.Get(0).([]app.MessageListDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageListAppService) CountAllUnReadMessage(userName string) ([]app.CountDTO, error) { + args := m.Called(userName) + return args.Get(0).([]app.CountDTO), args.Error(1) +} + +func (m *MockMessageListAppService) SetMessageIsRead(cmd *app.CmdToSetIsRead) error { + args := m.Called(cmd) + return args.Error(0) +} + +func (m *MockMessageListAppService) RemoveMessage(cmd *app.CmdToSetIsRead) error { + args := m.Called(cmd) + return args.Error(0) +} + +func TestGetInnerMessageQuick_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("GET", "/message_center/inner_quick?source=test", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer token") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetInnerMessageQuick_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("GET", "/message_center/inner_quick", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetInnerMessageQuick_ConvertError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + mockService.On("GetInnerMessageQuick", "testUser", mock.Anything). + Return(nil, int64(0), xerrors.New("error")) + + req, err := http.NewRequest("GET", "/message_center/inner_quick?source=test", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetInnerMessage_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("GET", "/message_center/inner?source=test", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetInnerMessage_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("GET", "/message_center/inner", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetInnerMessage_ConvertError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + mockService.On("GetInnerMessage", "testUser", mock.Anything). + Return(nil, int64(0), xerrors.New("error")) + + req, err := http.NewRequest("GET", "/message_center/inner?source=test", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestCountAllUnReadMessage_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("GET", "/message_center/inner/count", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestCountAllUnReadMessage_Unauthorized(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + mockService.On("CountAllUnReadMessage", "testUser"). + Return(0, xerrors.New("error")) + + req, err := http.NewRequest("GET", "/message_center/inner/count", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestSetMessageIsRead_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + messages := []messageStatus{ + {Source: "source1", EventId: "event1"}, + } + body, err := json.Marshal(messages) + if err != nil { + t.Fatal("Failed to marshal messages", err) + } + mockService.On("SetMessageIsRead", mock.Anything).Return(nil) + + req, err := http.NewRequest("PUT", "/message_center/inner", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusAccepted, w.Code) + mockService.AssertExpectations(t) +} + +func TestSetMessageIsRead_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("PUT", "/message_center/inner", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestSetMessageIsRead_ConvertError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + messages := []messageStatus{ + {Source: "source1", EventId: "event1"}, + } + body, err := json.Marshal(messages) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("SetMessageIsRead", mock.Anything).Return(xerrors.New("error")) + + req, err := http.NewRequest("PUT", "/message_center/inner", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) +} + +func TestRemoveMessage_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + messages := []messageStatus{ + {Source: "source1", EventId: "event1"}, + } + body, err := json.Marshal(messages) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("RemoveMessage", mock.Anything).Return(nil) + + req, err := http.NewRequest("DELETE", "/message_center/inner", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusAccepted, w.Code) + mockService.AssertExpectations(t) +} + +func TestRemoveMessage_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + req, err := http.NewRequest("DELETE", "/message_center/inner", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestRemoveMessage_ConvertError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessageListAppService) + AddRouterForMessageListController(r, mockService) + + messages := []messageStatus{ + {Source: "source1", EventId: "event1"}, + } + body, err := json.Marshal(messages) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("RemoveMessage", mock.Anything).Return(xerrors.New("error")) + + req, err := http.NewRequest("DELETE", "/message_center/inner", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) +} diff --git a/message/controller/push.go b/message/controller/push.go new file mode 100644 index 0000000..0da106a --- /dev/null +++ b/message/controller/push.go @@ -0,0 +1,166 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import ( + "net/http" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + "github.com/opensourceways/message-manager/common/user" + "golang.org/x/xerrors" + + commonctl "github.com/opensourceways/message-manager/common/controller" + "github.com/opensourceways/message-manager/message/app" +) + +func AddRouterForMessagePushController( + r *gin.Engine, + s app.MessagePushAppService, +) { + ctl := messagePushController{ + appService: s, + } + + v1 := r.Group("/message_center/config") + v1.GET("/push", ctl.GetPushConfig) + v1.POST("/push", ctl.AddPushConfig) + v1.PUT("/push", ctl.UpdatePushConfig) + v1.DELETE("/push", ctl.RemovePushConfig) +} + +type messagePushController struct { + appService app.MessagePushAppService +} + +// GetPushConfig +// @Summary GetPushConfig +// @Description get push config +// @Tags message_push +// @Accept json +// @Success 202 {object} app.MessagePushDTO +// @Failure 500 string system_error 查询失败 +// @Router /message_center/config/push [get] +// @Id getPushConfig +func (ctl *messagePushController) GetPushConfig(ctx *gin.Context) { + subsIdsStr := ctx.DefaultQuery("subscribe_id", "") + subsIds := strings.Split(subsIdsStr, ",") + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + + countPerPage, err := strconv.Atoi(ctx.Query("count_per_page")) + if err != nil { + return + } + pageNum, err := strconv.Atoi(ctx.Query("page")) + if err != nil { + return + } + + if data, err := ctl.appService.GetPushConfig(countPerPage, pageNum, userName, + subsIds); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"query_info": data}) + } +} + +// AddPushConfig +// @Summary AddPushConfig +// @Description add a new push_config +// @Tags message_push +// @Accept json +// @Param body body newPushConfigDTO true "newPushConfigDTO" +// @Success 202 string Accept 新增配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string system_error 新增配置失败 +// @Router /message_center/config/push [post] +// @Id addPushConfig +func (ctl *messagePushController) AddPushConfig(ctx *gin.Context) { + var req newPushConfigDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + + if err := ctl.appService.AddPushConfig(&cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("新增配置失败,err:%v", + err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "新增配置成功"}) + } +} + +// UpdatePushConfig +// @Summary UpdatePushConfig +// @Description update a push_config +// @Tags message_push +// @Param body body updatePushConfigDTO true "updatePushConfigDTO" +// @Accept json +// @Success 202 string Accept 更新配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string system_error 更新配置失败 +// @Router /message_center/config/push [put] +// @Id updatePushConfig +func (ctl *messagePushController) UpdatePushConfig(ctx *gin.Context) { + var req updatePushConfigDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + if err := ctl.appService.UpdatePushConfig(&cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("更新配置失败,err:%v", + err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "更新配置成功"}) + } +} + +// RemovePushConfig +// @Summary RemovePushConfig +// @Description delete a push_config +// @Tags message_push +// @Accept json +// @Param body body deletePushConfigDTO true "deletePushConfigDTO" +// @Success 202 string Accept 删除配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string system_error 删除配置失败 +// @Router /message_center/config/push [delete] +// @Id removePushConfig +func (ctl *messagePushController) RemovePushConfig(ctx *gin.Context) { + var req deletePushConfigDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + if err := ctl.appService.RemovePushConfig(&cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("删除配置失败,err:%v", + err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "删除配置成功"}) + } +} diff --git a/message/controller/push_request.go b/message/controller/push_request.go new file mode 100644 index 0000000..9dde077 --- /dev/null +++ b/message/controller/push_request.go @@ -0,0 +1,56 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import "github.com/opensourceways/message-manager/message/app" + +type deletePushConfigDTO struct { + SubscribeId int `gorm:"column:subscribe_id" json:"subscribe_id"` + RecipientId int64 `gorm:"column:recipient_id" json:"recipient_id"` +} + +func (req *deletePushConfigDTO) toCmd() (cmd app.CmdToDeletePushConfig, err error) { + cmd.SubscribeId = req.SubscribeId + cmd.RecipientId = req.RecipientId + return cmd, nil +} + +type newPushConfigDTO struct { + SubscribeId int `json:"subscribe_id"` + RecipientId int64 `json:"recipient_id"` + NeedMessage bool `json:"need_message"` + NeedPhone bool `json:"need_phone"` + NeedMail bool `json:"need_mail"` + NeedInnerMessage bool `json:"need_inner_message"` +} + +func (req *newPushConfigDTO) toCmd() (cmd app.CmdToAddPushConfig, err error) { + cmd.SubscribeId = req.SubscribeId + cmd.RecipientId = req.RecipientId + cmd.NeedMessage = req.NeedMessage + cmd.NeedPhone = req.NeedPhone + cmd.NeedMail = req.NeedMail + cmd.NeedInnerMessage = req.NeedInnerMessage + return cmd, nil +} + +type updatePushConfigDTO struct { + SubscribeId []string `json:"subscribe_id"` + RecipientId string `json:"recipient_id"` + NeedMessage bool `json:"need_message"` + NeedPhone bool `json:"need_phone"` + NeedMail bool `json:"need_mail"` + NeedInnerMessage bool `json:"need_inner_message"` +} + +func (req *updatePushConfigDTO) toCmd() (cmd app.CmdToUpdatePushConfig, err error) { + cmd.SubscribeId = req.SubscribeId + cmd.RecipientId = req.RecipientId + cmd.NeedMessage = req.NeedMessage + cmd.NeedPhone = req.NeedPhone + cmd.NeedMail = req.NeedMail + cmd.NeedInnerMessage = req.NeedInnerMessage + return cmd, nil +} diff --git a/message/controller/push_request_test.go b/message/controller/push_request_test.go new file mode 100644 index 0000000..4022afb --- /dev/null +++ b/message/controller/push_request_test.go @@ -0,0 +1,59 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeletePushConfigDTOToCmd(t *testing.T) { + req := &deletePushConfigDTO{ + SubscribeId: 123, + RecipientId: 456789, + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, 123, cmd.SubscribeId) + assert.Equal(t, int64(456789), cmd.RecipientId) +} + +func TestNewPushConfigDTOToCmd(t *testing.T) { + req := &newPushConfigDTO{ + SubscribeId: 321, + RecipientId: 987654, + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: false, + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, 321, cmd.SubscribeId) + assert.Equal(t, int64(987654), cmd.RecipientId) + assert.True(t, cmd.NeedMessage) + assert.False(t, cmd.NeedPhone) + assert.True(t, cmd.NeedMail) + assert.False(t, cmd.NeedInnerMessage) +} + +func TestUpdatePushConfigDTOToCmd(t *testing.T) { + req := &updatePushConfigDTO{ + SubscribeId: []string{"1", "2", "3"}, + RecipientId: "recipient123", + NeedMessage: true, + NeedPhone: true, + NeedMail: false, + NeedInnerMessage: true, + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, []string{"1", "2", "3"}, cmd.SubscribeId) + assert.Equal(t, "recipient123", cmd.RecipientId) + assert.True(t, cmd.NeedMessage) + assert.True(t, cmd.NeedPhone) + assert.False(t, cmd.NeedMail) + assert.True(t, cmd.NeedInnerMessage) +} diff --git a/message/controller/push_test.go b/message/controller/push_test.go new file mode 100644 index 0000000..475288f --- /dev/null +++ b/message/controller/push_test.go @@ -0,0 +1,323 @@ +package controller + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/app" +) + +// MockMessagePushAppService 是 MessagePushAppService 的模拟实现 +type MockMessagePushAppService struct { + mock.Mock +} + +func (m *MockMessagePushAppService) GetPushConfig(countPerPage, pageNum int, + userName string, subsIds []string) ([]app.MessagePushDTO, error) { + args := m.Called(countPerPage, pageNum, userName, subsIds) + return args.Get(0).([]app.MessagePushDTO), args.Error(1) +} + +func (m *MockMessagePushAppService) AddPushConfig(cmd *app.CmdToAddPushConfig) error { + args := m.Called(cmd) + return args.Error(0) +} + +func (m *MockMessagePushAppService) UpdatePushConfig(cmd *app.CmdToUpdatePushConfig) error { + args := m.Called(cmd) + return args.Error(0) +} + +func (m *MockMessagePushAppService) RemovePushConfig(cmd *app.CmdToDeletePushConfig) error { + args := m.Called(cmd) + return args.Error(0) +} + +func TestGetPushConfig_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + req, err := http.NewRequest("GET", + "/message_center/config/push?count_per_page=10&page=1&subscribe_id=sub1,sub2", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer token") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetPushConfig_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + req, err := http.NewRequest("GET", "/message_center/config/push", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestGetPushConfig_ServiceError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + userName := "testUser" + subsIds := []string{"sub1", "sub2"} + countPerPage, pageNum := 10, 1 + mockService.On("GetPushConfig", countPerPage, pageNum, userName, subsIds). + Return(nil, xerrors.New("service error")) + + req, err := http.NewRequest("GET", + "/message_center/config/push?count_per_page=10&page=1&subscribe_id=sub1,sub2", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer token") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestAddPushConfig_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + reqBody := app.CmdToAddPushConfig{ + SubscribeId: 1, + RecipientId: 123456, + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: true, + } + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("AddPushConfig", mock.Anything).Return(nil) + + req, err := http.NewRequest("POST", "/message_center/config/push", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusAccepted, w.Code) + mockService.AssertExpectations(t) +} + +func TestAddPushConfig_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + req, err := http.NewRequest("POST", "/message_center/config/push", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestAddPushConfig_ServiceError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + reqBody := app.CmdToAddPushConfig{ + SubscribeId: 1, + RecipientId: 123456, + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: true, + } + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("AddPushConfig", mock.Anything).Return(xerrors.New("service error")) + + req, err := http.NewRequest("POST", "/message_center/config/push", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) +} + +func TestUpdatePushConfig_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + reqBody := app.CmdToUpdatePushConfig{ + SubscribeId: []string{"1", "2"}, + RecipientId: "recipient@example.com", + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: true, + } + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("UpdatePushConfig", mock.Anything).Return(nil) + + req, err := http.NewRequest("PUT", "/message_center/config/push", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusAccepted, w.Code) + mockService.AssertExpectations(t) +} + +func TestUpdatePushConfig_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + req, err := http.NewRequest("PUT", "/message_center/config/push", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestUpdatePushConfig_ServiceError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + reqBody := app.CmdToUpdatePushConfig{ + SubscribeId: []string{"1", "2"}, + RecipientId: "recipient@example.com", + NeedMessage: true, + NeedPhone: false, + NeedMail: true, + NeedInnerMessage: true, + } + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("UpdatePushConfig", mock.Anything).Return(xerrors.New("service error")) + + req, err := http.NewRequest("PUT", "/message_center/config/push", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) +} + +func TestRemovePushConfig_Success(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + reqBody := app.CmdToDeletePushConfig{ + SubscribeId: 1, + RecipientId: 123456, + } + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("RemovePushConfig", mock.Anything).Return(nil) + + req, err := http.NewRequest("DELETE", "/message_center/config/push", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusAccepted, w.Code) + mockService.AssertExpectations(t) +} + +func TestRemovePushConfig_BindError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + req, err := http.NewRequest("DELETE", "/message_center/config/push", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestRemovePushConfig_ServiceError(t *testing.T) { + gin.SetMode(gin.TestMode) + r := gin.Default() + mockService := new(MockMessagePushAppService) + AddRouterForMessagePushController(r, mockService) + + reqBody := app.CmdToDeletePushConfig{ + SubscribeId: 1, + RecipientId: 123456, + } + body, err := json.Marshal(reqBody) + if err != nil { + t.Fatal("Failed to marshal messages:", err) + } + mockService.On("RemovePushConfig", mock.Anything).Return(xerrors.New("service error")) + + req, err := http.NewRequest("DELETE", "/message_center/config/push", bytes.NewBuffer(body)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + assert.Equal(t, http.StatusInternalServerError, w.Code) +} diff --git a/message/controller/recipient.go b/message/controller/recipient.go new file mode 100644 index 0000000..c3e2911 --- /dev/null +++ b/message/controller/recipient.go @@ -0,0 +1,206 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import ( + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "github.com/opensourceways/message-manager/common/user" + "golang.org/x/xerrors" + + commonctl "github.com/opensourceways/message-manager/common/controller" + "github.com/opensourceways/message-manager/message/app" +) + +func AddRouterForMessageRecipientController( + r *gin.Engine, + s app.MessageRecipientAppService, +) { + ctl := messageRecipientController{ + appService: s, + } + + v1 := r.Group("/message_center/config") + v1.GET("/recipient", ctl.GetRecipientConfig) + v1.POST("/recipient", ctl.AddRecipientConfig) + v1.POST("/recipient/sync", ctl.SyncUserInfo) + v1.PUT("/recipient", ctl.UpdateRecipientConfig) + v1.DELETE("/recipient", ctl.RemoveRecipientConfig) +} + +type messageRecipientController struct { + appService app.MessageRecipientAppService +} + +// GetRecipientConfig +// @Summary GetRecipientConfig +// @Description get recipient config +// @Tags recipient +// @Accept json +// @Success 202 integer count +// @Failure 500 string system_error 查询失败 +// @Router /message_center/config/recipient [get] +// @Id getRecipientConfig +func (ctl *messageRecipientController) GetRecipientConfig(ctx *gin.Context) { + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + countPerPage, err := strconv.Atoi(ctx.Query("count_per_page")) + if err != nil { + return + } + pageNum, err := strconv.Atoi(ctx.Query("page")) + if err != nil { + return + } + if data, count, err := ctl.appService.GetRecipientConfig(countPerPage, pageNum, userName); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": err}) + } else { + ctx.JSON(http.StatusOK, gin.H{"query_info": data, "count": count}) + } +} + +// AddRecipientConfig +// @Summary AddRecipientConfig +// @Description add recipient config +// @Tags recipient +// @Param body body newRecipientDTO true "newRecipientDTO" +// @Accept json +// @Success 202 string accepted 新增配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string server_error 新增配置失败 +// @Router /message_center/config/recipient [post] +// @Id addRecipientConfig +func (ctl *messageRecipientController) AddRecipientConfig(ctx *gin.Context) { + var req newRecipientDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if err := ctl.appService.AddRecipientConfig(userName, &cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("新增配置失败,err:%v", + err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "新增配置成功"}) + } +} + +// UpdateRecipientConfig +// @Summary UpdateRecipientConfig +// @Description update recipient config +// @Tags recipient +// @Param body body updateRecipientDTO true "updateRecipientDTO" +// @Accept json +// @Success 202 string accepted 更新配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string server_error 更新配置失败 +// @Router /message_center/config/recipient [put] +// @Id updateRecipientConfig +func (ctl *messageRecipientController) UpdateRecipientConfig(ctx *gin.Context) { + var req updateRecipientDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if err := ctl.appService.UpdateRecipientConfig(userName, &cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("更新配置失败,err:%v", + err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "更新配置成功"}) + } +} + +// RemoveRecipientConfig +// @Summary RemoveRecipientConfig +// @Description remove recipient config +// @Tags recipient +// @Param body body updateRecipientDTO true "updateRecipientDTO" +// @Accept json +// @Success 202 string accepted 删除配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string server_error 删除配置失败 +// @Router /message_center/config/recipient [delete] +// @Id removeRecipientConfig +func (ctl *messageRecipientController) RemoveRecipientConfig(ctx *gin.Context) { + var req updateRecipientDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if err := ctl.appService.UpdateRecipientConfig(userName, &cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("删除配置失败,err:%v", + err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "删除配置成功"}) + } +} + +// SyncUserInfo +// @Summary SyncUserInfo +// @Description sync user info +// @Tags recipient +// @Param body body syncUserInfoDTO true "syncUserInfoDTO" +// @Accept json +// @Success 202 string accepted 同步用户信息成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 500 string server_error 同步用户信息失败 +// @Router /message_center/config/recipient/sync [post] +// @Id syncUserInfo +func (ctl *messageRecipientController) SyncUserInfo(ctx *gin.Context) { + var req syncUserInfoDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + if data, err := ctl.appService.SyncUserInfo(&cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"error": xerrors.Errorf("同步用户信息失败,"+ + "err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"newId": data, "message": "同步用户信息成功"}) + } +} diff --git a/message/controller/recipient_request.go b/message/controller/recipient_request.go new file mode 100644 index 0000000..b5140dd --- /dev/null +++ b/message/controller/recipient_request.go @@ -0,0 +1,69 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import "github.com/opensourceways/message-manager/message/app" + +type syncUserInfoDTO struct { + Mail string `json:"mail"` + Phone string `json:"phone"` + CountryCode string `json:"country_code"` + UserName string `json:"user_name"` + GiteeUserName string `json:"gitee_user_name"` +} + +func (req *syncUserInfoDTO) toCmd() (cmd app.CmdToSyncUserInfo, err error) { + cmd.Mail = req.Mail + cmd.Phone = req.Phone + cmd.CountryCode = req.CountryCode + cmd.UserName = req.UserName + cmd.GiteeUserName = req.GiteeUserName + return +} + +type newRecipientDTO struct { + Name string `gorm:"column:recipient_name" json:"recipient_id"` + Mail string `gorm:"column:mail" json:"mail"` + Message string `gorm:"column:message" json:"message"` + Phone string `gorm:"column:phone" json:"phone"` + Remark string `gorm:"column:remark" json:"remark"` +} + +func (req *newRecipientDTO) toCmd() (cmd app.CmdToAddRecipient, err error) { + cmd.Name = req.Name + cmd.Mail = req.Mail + cmd.Message = req.Message + cmd.Phone = req.Phone + cmd.Remark = req.Remark + return +} + +type updateRecipientDTO struct { + Id string `gorm:"column:id" json:"id"` + Name string `gorm:"column:recipient_name" json:"recipient_id"` + Mail string `gorm:"column:mail" json:"mail"` + Message string `gorm:"column:message" json:"message"` + Phone string `gorm:"column:phone" json:"phone"` + Remark string `gorm:"column:remark" json:"remark"` +} + +func (req *updateRecipientDTO) toCmd() (cmd app.CmdToUpdateRecipient, err error) { + cmd.Id = req.Id + cmd.Name = req.Name + cmd.Mail = req.Mail + cmd.Message = req.Message + cmd.Phone = req.Phone + cmd.Remark = req.Remark + return +} + +type deleteRecipientDTO struct { + RecipientId string `gorm:"column:recipient_id" json:"recipient_id"` +} + +func (req *deleteRecipientDTO) toCmd() (cmd app.CmdToDeleteRecipient, err error) { + cmd.RecipientId = req.RecipientId + return +} diff --git a/message/controller/recipient_request_test.go b/message/controller/recipient_request_test.go new file mode 100644 index 0000000..8dc1e47 --- /dev/null +++ b/message/controller/recipient_request_test.go @@ -0,0 +1,73 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSyncUserInfoDTOToCmd(t *testing.T) { + req := &syncUserInfoDTO{ + Mail: "user@example.com", + Phone: "1234567890", + CountryCode: "86", + UserName: "testuser", + GiteeUserName: "giteeuser", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "user@example.com", cmd.Mail) + assert.Equal(t, "1234567890", cmd.Phone) + assert.Equal(t, "86", cmd.CountryCode) + assert.Equal(t, "testuser", cmd.UserName) + assert.Equal(t, "giteeuser", cmd.GiteeUserName) +} + +func TestNewRecipientDTOToCmd(t *testing.T) { + req := &newRecipientDTO{ + Name: "Recipient Name", + Mail: "recipient@example.com", + Message: "Welcome!", + Phone: "0987654321", + Remark: "Important recipient", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "Recipient Name", cmd.Name) + assert.Equal(t, "recipient@example.com", cmd.Mail) + assert.Equal(t, "Welcome!", cmd.Message) + assert.Equal(t, "0987654321", cmd.Phone) + assert.Equal(t, "Important recipient", cmd.Remark) +} + +func TestUpdateRecipientDTOToCmd(t *testing.T) { + req := &updateRecipientDTO{ + Id: "recipient-id-123", + Name: "Updated Recipient Name", + Mail: "updated@example.com", + Message: "Updated message", + Phone: "1122334455", + Remark: "Updated remark", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "recipient-id-123", cmd.Id) + assert.Equal(t, "Updated Recipient Name", cmd.Name) + assert.Equal(t, "updated@example.com", cmd.Mail) + assert.Equal(t, "Updated message", cmd.Message) + assert.Equal(t, "1122334455", cmd.Phone) + assert.Equal(t, "Updated remark", cmd.Remark) +} + +func TestDeleteRecipientDTOToCmd(t *testing.T) { + req := &deleteRecipientDTO{ + RecipientId: "recipient-id-456", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "recipient-id-456", cmd.RecipientId) +} diff --git a/message/controller/recipient_test.go b/message/controller/recipient_test.go new file mode 100644 index 0000000..4264a48 --- /dev/null +++ b/message/controller/recipient_test.go @@ -0,0 +1,240 @@ +package controller + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/app" +) + +// Mock for the MessageRecipientAppService +type MockMessageRecipientAppService struct { + mock.Mock +} + +func (m *MockMessageRecipientAppService) GetRecipientConfig(countPerPage, + pageNum int, userName string) ([]app.MessageRecipientDTO, int64, error) { + args := m.Called(countPerPage, pageNum, userName) + return args.Get(0).([]app.MessageRecipientDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageRecipientAppService) AddRecipientConfig(userName string, + cmd *app.CmdToAddRecipient) error { + return m.Called(userName, cmd).Error(0) +} + +func (m *MockMessageRecipientAppService) UpdateRecipientConfig(userName string, + cmd *app.CmdToUpdateRecipient) error { + return m.Called(userName, cmd).Error(0) +} + +func (m *MockMessageRecipientAppService) RemoveRecipientConfig(userName string, + cmd *app.CmdToDeleteRecipient) error { + return m.Called(userName, cmd).Error(0) +} + +func (m *MockMessageRecipientAppService) SyncUserInfo(cmd *app.CmdToSyncUserInfo) (uint, error) { + args := m.Called(cmd) + return args.Get(0).(uint), args.Error(1) +} + +func TestGetRecipientConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageRecipientAppService) + + AddRouterForMessageRecipientController(router, mockAppService) + + // Successful case + mockAppService.On("GetRecipientConfig", 10, 1, "testUser"). + Return([]app.MessageRecipientDTO{{}}, int64(1), nil) + + req, err := http.NewRequest(http.MethodGet, + "/message_center/config/recipient?count_per_page=10&page=1", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case + mockAppService.On("GetRecipientConfig", 10, 1, "testUser"). + Return(nil, int64(0), xerrors.New("db error")) + + recorder = httptest.NewRecorder() + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestAddRecipientConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageRecipientAppService) + AddRouterForMessageRecipientController(router, mockAppService) + // Successful case + mockAppService.On("AddRecipientConfig", "testUser", mock.Anything). + Return(nil) + + reqBody := `{"name":"recipient1"}` + req, err := http.NewRequest(http.MethodPost, "/message_center/config/recipient", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case + mockAppService.On("AddRecipientConfig", "testUser", mock.Anything). + Return(xerrors.New("add error")) + + reqBody = `{"name":"recipient1"}` + req, err = http.NewRequest(http.MethodPost, "/message_center/config/recipient", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestUpdateRecipientConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageRecipientAppService) + AddRouterForMessageRecipientController(router, mockAppService) + + // Successful case + mockAppService.On("UpdateRecipientConfig", "testUser", mock.Anything). + Return(nil) + + reqBody := `{"id":"1","name":"updatedRecipient"}` + req, err := http.NewRequest(http.MethodPut, "/message_center/config/recipient", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case + mockAppService.On("UpdateRecipientConfig", "testUser", mock.Anything). + Return(xerrors.New("update error")) + + reqBody = `{"id":"1","name":"updatedRecipient"}` + req, err = http.NewRequest(http.MethodPut, "/message_center/config/recipient", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestRemoveRecipientConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageRecipientAppService) + AddRouterForMessageRecipientController(router, mockAppService) + + // Successful case + mockAppService.On("RemoveRecipientConfig", "testUser", mock.Anything). + Return(nil) + + reqBody := `{"id":"1"}` + req, err := http.NewRequest(http.MethodDelete, "/message_center/config/recipient", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case + mockAppService.On("RemoveRecipientConfig", "testUser", mock.Anything). + Return(xerrors.New("delete error")) + + reqBody = `{"id":"1"}` + req, err = http.NewRequest(http.MethodDelete, "/message_center/config/recipient", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestSyncUserInfo(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageRecipientAppService) + AddRouterForMessageRecipientController(router, mockAppService) + + // Successful case + mockAppService.On("SyncUserInfo", mock.Anything).Return(uint(1), nil) + + reqBody := `{"user_info":"example"}` + req, err := http.NewRequest(http.MethodPost, "/message_center/config/recipient/sync", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusAccepted, recorder.Code) + + // Error case + mockAppService.On("SyncUserInfo", mock.Anything).Return(uint(0), xerrors.New("sync error")) + + reqBody = `{"user_info":"example"}` + req, err = http.NewRequest(http.MethodPost, "/message_center/config/recipient/sync", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusAccepted, recorder.Code) +} diff --git a/message/controller/subscribe.go b/message/controller/subscribe.go new file mode 100644 index 0000000..5c9b8e2 --- /dev/null +++ b/message/controller/subscribe.go @@ -0,0 +1,237 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/opensourceways/message-manager/common/user" + "golang.org/x/xerrors" + + commonctl "github.com/opensourceways/message-manager/common/controller" + "github.com/opensourceways/message-manager/message/app" +) + +func AddRouterForMessageSubscribeController( + r *gin.Engine, + s app.MessageSubscribeAppService, +) { + ctl := messageSubscribeController{ + appService: s, + } + v1 := r.Group("/message_center/config") + v1.GET("/subs", ctl.GetSubsConfig) + v1.GET("/subs/all", ctl.GetAllSubsConfig) + v1.POST("/subs", ctl.AddSubsConfig) + v1.POST("/subs_new", ctl.SaveFilter) + v1.PUT("/subs", ctl.UpdateSubsConfig) + v1.DELETE("/subs", ctl.RemoveSubsConfig) +} + +type messageSubscribeController struct { + appService app.MessageSubscribeAppService +} + +// GetAllSubsConfig +// @Summary GetAllSubsConfig +// @Description get all subscribe_config +// @Tags message_subscribe +// @Accept json +// @Success 202 {object} app.MessageSubscribeDTO +// @Failure 401 string unauthorized 用户未授权 +// @Failure 500 string system_error 查询失败 +// @Router /message_center/config/subs/all [get] +// @Id getAllSubsConfig +func (ctl *messageSubscribeController) GetAllSubsConfig(ctx *gin.Context) { + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + data, err := ctl.appService.GetAllSubsConfig(userName) + if err != nil { + ctx.JSON(http.StatusInternalServerError, + gin.H{"error": xerrors.Errorf("查询失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"query_info": data}) + } +} + +// GetSubsConfig +// @Summary GetSubsConfig +// @Description get subscribe_config +// @Tags message_subscribe +// @Accept json +// @Success 202 {object} app.MessageSubscribeDTO +// @Failure 401 string unauthorized 用户未授权 +// @Failure 500 string system_error 查询失败 +// @Router /message_center/config/subs [get] +// @Id getSubsConfig +func (ctl *messageSubscribeController) GetSubsConfig(ctx *gin.Context) { + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if data, count, err := ctl.appService.GetSubsConfig(userName); err != nil { + ctx.JSON(http.StatusInternalServerError, + gin.H{"error": xerrors.Errorf("查询失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"query_info": data, "count": count}) + } +} + +// SaveFilter +// @Summary SaveFilter +// @Description save custom filter +// @Tags message_subscribe +// @Accept json +// @Param body body subscribeDTO true "subscribeDTO" +// @Success 202 string accept 保存成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 401 string unauthorized 用户未授权 +// @Failure 500 string system_error 保存失败 +// @Router /message_center/config/subs_new [post] +// @Id saveFilter +func (ctl *messageSubscribeController) SaveFilter(ctx *gin.Context) { + var req subscribeDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, + xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + if err := ctl.appService.SaveFilter(userName, &cmd); err != nil { + ctx.JSON(http.StatusInternalServerError, + gin.H{"error": xerrors.Errorf("保存失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "保存成功"}) + } +} + +// AddSubsConfig +// @Summary AddSubsConfig +// @Description add a subscribe_config +// @Tags message_subscribe +// @Param body body newSubscribeDTO true "newSubscribeDTO" +// @Accept json +// @Success 202 string Accept 新增配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 401 string unauthorized 用户未授权 +// @Failure 500 string system_error 新增配置失败 +// @Router /message_center/config/subs [post] +// @Id addSubsConfig +func (ctl *messageSubscribeController) AddSubsConfig(ctx *gin.Context) { + var req newSubscribeDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, + xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + data, err := ctl.appService.AddSubsConfig(userName, &cmd) + if err != nil { + ctx.JSON(http.StatusInternalServerError, + gin.H{"error": xerrors.Errorf("新增配置失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"newId": data, "message": "新增配置成功"}) + } +} + +// UpdateSubsConfig +// @Summary UpdateSubsConfig +// @Description update a subscribe_config +// @Tags message_subscribe +// @Param body body updateSubscribeDTO true "updateSubscribeDTO" +// @Accept json +// @Success 202 string Accept 更新配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 401 string unauthorized 用户未授权 +// @Failure 500 string system_error 更新配置成功 +// @Router /message_center/config/subs [put] +// @Id updateSubsConfig +func (ctl *messageSubscribeController) UpdateSubsConfig(ctx *gin.Context) { + var req updateSubscribeDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, + xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + err = ctl.appService.UpdateSubsConfig(userName, &cmd) + if err != nil { + ctx.JSON(http.StatusInternalServerError, gin.H{"message": "更新配置成功", "error": err}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "更新配置成功"}) + } +} + +// RemoveSubsConfig +// @Summary RemoveSubsConfig +// @Description delete a subscribe_config by source and type +// @Tags message_subscribe +// @Accept json +// @Param body body deleteSubscribeDTO true "deleteSubscribeDTO" +// @Success 202 string Accept 删除配置成功 +// @Failure 400 string bad_request 无法解析请求正文 +// @Failure 401 string unauthorized 用户未授权 +// @Failure 500 string system_error 删除配置失败 +// @Router /message_center/config/subs [delete] +// @Id removeSubsConfig +func (ctl *messageSubscribeController) RemoveSubsConfig(ctx *gin.Context) { + var req deleteSubscribeDTO + if err := ctx.BindJSON(&req); err != nil { + commonctl.SendBadRequestParam(ctx, xerrors.Errorf("failed to bind params, %w", err)) + return + } + cmd, err := req.toCmd() + if err != nil { + commonctl.SendBadRequestParam(ctx, + xerrors.Errorf("failed to convert req to cmd, %w", err)) + return + } + userName, err := user.GetEulerUserName(ctx) + if err != nil { + commonctl.SendUnauthorized(ctx, xerrors.Errorf("get username failed, err:%v", err)) + return + } + err = ctl.appService.RemoveSubsConfig(userName, &cmd) + if err != nil { + ctx.JSON(http.StatusInternalServerError, + gin.H{"error": xerrors.Errorf("删除配置失败,err:%v", err)}) + } else { + ctx.JSON(http.StatusAccepted, gin.H{"message": "删除配置成功"}) + } +} diff --git a/message/controller/subscribe_request.go b/message/controller/subscribe_request.go new file mode 100644 index 0000000..3b8c8a7 --- /dev/null +++ b/message/controller/subscribe_request.go @@ -0,0 +1,95 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package controller + +import ( + "github.com/opensourceways/message-manager/message/app" + "gorm.io/datatypes" +) + +type subscribeDTO struct { + queryInnerParams + SpecVersion string `json:"spec_version"` + ModeName string `json:"mode_name"` +} + +func (req *subscribeDTO) toCmd() (cmd app.CmdToGetSubscribe, err error) { + cmd.Source = req.Source + cmd.EventType = req.EventType + cmd.IsRead = req.IsRead + cmd.KeyWord = req.KeyWord + cmd.IsBot = req.IsBot + cmd.GiteeSigs = req.GiteeSigs + cmd.Repos = req.Repos + cmd.CountPerPage = req.CountPerPage + cmd.PageNum = req.PageNum + cmd.StartTime = req.StartTime + cmd.EndTime = req.EndTime + cmd.MySig = req.MySig + cmd.MyManagement = req.MyManagement + cmd.PrState = req.PrState + cmd.PrCreator = req.PrCreator + cmd.PrAssignee = req.PrAssignee + cmd.IssueState = req.IssueState + cmd.IssueCreator = req.IssueCreator + cmd.IssueAssignee = req.IssueAssignee + cmd.NoteType = req.NoteType + cmd.About = req.About + cmd.BuildStatus = req.BuildStatus + cmd.BuildOwner = req.BuildOwner + cmd.BuildCreator = req.BuildCreator + cmd.BuildEnv = req.BuildEnv + cmd.MeetingAction = req.MeetingAction + cmd.MeetingSigGroup = req.MeetingSigGroup + cmd.MeetingStartTime = req.MeetingStartTime + cmd.MeetingEndTime = req.MeetingEndTime + cmd.CVEComponent = req.CVEComponent + cmd.CVEState = req.CVEState + cmd.CVEAffected = req.CVEAffected + cmd.SpecVersion = req.SpecVersion + cmd.ModeName = req.ModeName + return cmd, nil +} + +type newSubscribeDTO struct { + Source string `json:"source"` + EventType string `json:"event_type"` + SpecVersion string `json:"spec_version"` + ModeFilter datatypes.JSON `json:"mode_filter" swaggerignore:"true"` + ModeName string `json:"mode_name"` +} + +func (req *newSubscribeDTO) toCmd() (cmd app.CmdToAddSubscribe, err error) { + cmd.Source = req.Source + cmd.EventType = req.EventType + cmd.SpecVersion = req.SpecVersion + cmd.ModeFilter = req.ModeFilter + cmd.ModeName = req.ModeName + return +} + +type updateSubscribeDTO struct { + Source string `json:"source"` + OldName string `json:"old_name"` + NewName string `json:"new_name"` +} + +func (req *updateSubscribeDTO) toCmd() (cmd app.CmdToUpdateSubscribe, err error) { + cmd.Source = req.Source + cmd.OldName = req.OldName + cmd.NewName = req.NewName + return +} + +type deleteSubscribeDTO struct { + Source string `json:"source"` + ModeName string `json:"mode_name"` +} + +func (req *deleteSubscribeDTO) toCmd() (cmd app.CmdToDeleteSubscribe, err error) { + cmd.Source = req.Source + cmd.ModeName = req.ModeName + return +} diff --git a/message/controller/subscribe_request_test.go b/message/controller/subscribe_request_test.go new file mode 100644 index 0000000..eaec512 --- /dev/null +++ b/message/controller/subscribe_request_test.go @@ -0,0 +1,87 @@ +package controller + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "gorm.io/datatypes" +) + +func TestSubscribeDTOToCmd(t *testing.T) { + req := &subscribeDTO{ + queryInnerParams: queryInnerParams{ + Source: "test_source", + EventType: "test_event", + IsRead: "true", + KeyWord: "keyword", + IsBot: "false", + GiteeSigs: "sig", + Repos: "repo", + CountPerPage: 10, + PageNum: 1, + StartTime: "2024-01-01T00:00:00Z", + EndTime: "2024-01-02T00:00:00Z", + MySig: "my_sig", + MyManagement: "management", + PrState: "open", + PrCreator: "creator", + PrAssignee: "assignee", + IssueState: "closed", + IssueCreator: "issue_creator", + IssueAssignee: "issue_assignee", + NoteType: "note", + About: "about", + BuildStatus: "success", + BuildOwner: "owner", + BuildCreator: "builder", + BuildEnv: "production", + MeetingAction: "create", + MeetingSigGroup: "sig_group", + MeetingStartTime: "2024-01-01T09:00:00Z", + MeetingEndTime: "2024-01-01T10:00:00Z", + CVEComponent: "cve_component", + CVEState: "open", + CVEAffected: "yes", + }, + SpecVersion: "1.0", + ModeName: "test_mode", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "test_source", cmd.Source) + assert.Equal(t, "test_event", cmd.EventType) + assert.Equal(t, "1.0", cmd.SpecVersion) + assert.Equal(t, "test_mode", cmd.ModeName) + // 继续验证其他字段... +} + +func TestNewSubscribeDTOToCmd(t *testing.T) { + req := &newSubscribeDTO{ + Source: "new_source", + EventType: "new_event", + SpecVersion: "1.1", + ModeFilter: datatypes.JSON([]byte(`{"filter": "value"}`)), + ModeName: "new_mode", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "new_source", cmd.Source) + assert.Equal(t, "new_event", cmd.EventType) + assert.Equal(t, "1.1", cmd.SpecVersion) + assert.JSONEq(t, `{"filter": "value"}`, string(cmd.ModeFilter)) + assert.Equal(t, "new_mode", cmd.ModeName) +} + +func TestDeleteSubscribeDTOToCmd(t *testing.T) { + req := &deleteSubscribeDTO{ + Source: "delete_source", + ModeName: "delete_mode", + } + + cmd, err := req.toCmd() + assert.NoError(t, err) + assert.Equal(t, "delete_source", cmd.Source) + assert.Equal(t, "delete_mode", cmd.ModeName) +} diff --git a/message/controller/subscribe_test.go b/message/controller/subscribe_test.go new file mode 100644 index 0000000..f5f587a --- /dev/null +++ b/message/controller/subscribe_test.go @@ -0,0 +1,272 @@ +package controller + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "golang.org/x/xerrors" + + "github.com/opensourceways/message-manager/message/app" +) + +// Mock for the MessageSubscribeAppService +type MockMessageSubscribeAppService struct { + mock.Mock +} + +func (m *MockMessageSubscribeAppService) GetAllSubsConfig(userName string) ( + []app.MessageSubscribeDTO, error) { + args := m.Called(userName) + return args.Get(0).([]app.MessageSubscribeDTO), args.Error(1) +} + +func (m *MockMessageSubscribeAppService) GetSubsConfig(userName string) ( + []app.MessageSubscribeDTO, int64, error) { + args := m.Called(userName) + return args.Get(0).([]app.MessageSubscribeDTO), args.Get(1).(int64), args.Error(2) +} + +func (m *MockMessageSubscribeAppService) SaveFilter(userName string, + cmd *app.CmdToGetSubscribe) error { + return m.Called(userName, cmd).Error(0) +} + +func (m *MockMessageSubscribeAppService) AddSubsConfig(userName string, + cmd *app.CmdToAddSubscribe) ([]uint, error) { + args := m.Called(userName, cmd) + return args.Get(0).([]uint), args.Error(1) +} + +func (m *MockMessageSubscribeAppService) RemoveSubsConfig(userName string, + cmd *app.CmdToDeleteSubscribe) error { + return m.Called(userName, cmd).Error(0) +} + +func TestGetAllSubsConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageSubscribeAppService) + AddRouterForMessageSubscribeController(router, mockAppService) + + // Successful case + mockAppService.On("GetAllSubsConfig", "testUser"). + Return([]app.MessageSubscribeDTO{{}}, nil) + + req, err := http.NewRequest(http.MethodGet, "/message_center/config/subs/all", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case + mockAppService.On("GetAllSubsConfig", "testUser"). + Return(nil, xerrors.New("db error")) + + recorder = httptest.NewRecorder() + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestGetSubsConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageSubscribeAppService) + AddRouterForMessageSubscribeController(router, mockAppService) + + // Successful case + mockAppService.On("GetSubsConfig", "testUser"). + Return([]app.MessageSubscribeDTO{{}}, int64(1), nil) + + req, err := http.NewRequest(http.MethodGet, "/message_center/config/subs", nil) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case + mockAppService.On("GetSubsConfig", "testUser"). + Return(nil, int64(0), xerrors.New("db error")) + + recorder = httptest.NewRecorder() + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestSaveFilter(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageSubscribeAppService) + AddRouterForMessageSubscribeController(router, mockAppService) + + // Successful case + mockAppService.On("SaveFilter", "testUser", mock.Anything). + Return(nil) + + reqBody := `{"filter":"example"}` + req, err := http.NewRequest(http.MethodPost, "/message_center/config/subs_new", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case (binding error) + reqBody = `{"invalid_field":"example"}` + req, err = http.NewRequest(http.MethodPost, "/message_center/config/subs_new", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case (save error) + mockAppService.On("SaveFilter", "testUser", mock.Anything). + Return(xerrors.New("save error")) + + reqBody = `{"filter":"example"}` + req, err = http.NewRequest(http.MethodPost, "/message_center/config/subs_new", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestAddSubsConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageSubscribeAppService) + AddRouterForMessageSubscribeController(router, mockAppService) + + // Successful case + mockAppService.On("AddSubsConfig", "testUser", mock.Anything). + Return([]uint{1}, nil) + + reqBody := `{"name":"new subscription"}` + req, err := http.NewRequest(http.MethodPost, "/message_center/config/subs", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case (binding error) + reqBody = `{"invalid_field":"new subscription"}` + req, err = http.NewRequest(http.MethodPost, "/message_center/config/subs", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case (add error) + mockAppService.On("AddSubsConfig", "testUser", mock.Anything). + Return(nil, xerrors.New("add error")) + + reqBody = `{"name":"new subscription"}` + req, err = http.NewRequest(http.MethodPost, "/message_center/config/subs", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} + +func TestRemoveSubsConfig(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.Default() + mockAppService := new(MockMessageSubscribeAppService) + AddRouterForMessageSubscribeController(router, mockAppService) + + // Successful case + mockAppService.On("RemoveSubsConfig", "testUser", mock.Anything). + Return(nil) + + reqBody := `{"id":1}` + req, err := http.NewRequest(http.MethodDelete, "/message_center/config/subs", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + req.Header.Set("Authorization", "Bearer testToken") + req.Header.Set("Content-Type", "application/json") + recorder := httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case (binding error) + reqBody = `{"invalid_field":1}` + req, err = http.NewRequest(http.MethodDelete, "/message_center/config/subs", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) + + // Error case (remove error) + mockAppService.On("RemoveSubsConfig", "testUser", mock.Anything). + Return(xerrors.New("remove error")) + + reqBody = `{"id":1}` + req, err = http.NewRequest(http.MethodDelete, "/message_center/config/subs", + strings.NewReader(reqBody)) + if err != nil { + t.Fatal("Failed to create request:", err) + } + recorder = httptest.NewRecorder() + + router.ServeHTTP(recorder, req) + + assert.Equal(t, http.StatusUnauthorized, recorder.Code) +} diff --git a/message/domain/do.go b/message/domain/do.go new file mode 100644 index 0000000..6ad41a8 --- /dev/null +++ b/message/domain/do.go @@ -0,0 +1,31 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package domain + +import ( + "github.com/opensourceways/message-manager/message/infrastructure" +) + +type MessageListDO = infrastructure.MessageListDAO +type MessagePushDO = infrastructure.MessagePushDAO +type MessageRecipientDO = infrastructure.MessageRecipientDAO +type MessageSubscribeDO = infrastructure.MessageSubscribeDAO +type MessageSubscribeDOWithPushConfig = infrastructure.MessageSubscribeDAOWithPushConfig +type CountDO = infrastructure.CountDAO + +type CmdToGetInnerMessageQuick = infrastructure.CmdToGetInnerMessageQuick +type CmdToGetInnerMessage = infrastructure.CmdToGetInnerMessage +type CmdToSetIsRead = infrastructure.CmdToSetIsRead +type CmdToAddPushConfig = infrastructure.CmdToAddPushConfig +type CmdToUpdatePushConfig = infrastructure.CmdToUpdatePushConfig +type CmdToDeletePushConfig = infrastructure.CmdToDeletePushConfig +type CmdToAddRecipient = infrastructure.CmdToAddRecipient +type CmdToUpdateRecipient = infrastructure.CmdToUpdateRecipient +type CmdToDeleteRecipient = infrastructure.CmdToDeleteRecipient +type CmdToSyncUserInfo = infrastructure.CmdToSyncUserInfo +type CmdToGetSubscribe = infrastructure.CmdToGetSubscribe +type CmdToAddSubscribe = infrastructure.CmdToAddSubscribe +type CmdToUpdateSubscribe = infrastructure.CmdToUpdateSubscribe +type CmdToDeleteSubscribe = infrastructure.CmdToDeleteSubscribe diff --git a/message/domain/message.go b/message/domain/message.go new file mode 100644 index 0000000..f1d8ddb --- /dev/null +++ b/message/domain/message.go @@ -0,0 +1,14 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package domain + +type MessageListAdapter interface { + GetInnerMessageQuick(cmd CmdToGetInnerMessageQuick, serName string) ( + []MessageListDO, int64, error) + GetInnerMessage(cmd CmdToGetInnerMessage, userName string) ([]MessageListDO, int64, error) + CountAllUnReadMessage(userName string) ([]CountDO, error) + SetMessageIsRead(source, eventId string) error + RemoveMessage(source, eventId string) error +} diff --git a/message/domain/push.go b/message/domain/push.go new file mode 100644 index 0000000..4756b38 --- /dev/null +++ b/message/domain/push.go @@ -0,0 +1,13 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package domain + +type MessagePushAdapter interface { + GetPushConfig(subsIds []string, countPerPage, pageNum int, + userName string) ([]MessagePushDO, error) + AddPushConfig(cmd CmdToAddPushConfig) error + UpdatePushConfig(cmd CmdToUpdatePushConfig) error + RemovePushConfig(cmd CmdToDeletePushConfig) error +} diff --git a/message/domain/recipient.go b/message/domain/recipient.go new file mode 100644 index 0000000..1215433 --- /dev/null +++ b/message/domain/recipient.go @@ -0,0 +1,14 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package domain + +type MessageRecipientAdapter interface { + GetRecipientConfig(countPerPage, pageNum int, userName string) ([]MessageRecipientDO, int64, + error) + AddRecipientConfig(cmd CmdToAddRecipient, userName string) error + UpdateRecipientConfig(cmd CmdToUpdateRecipient, userName string) error + RemoveRecipientConfig(cmd CmdToDeleteRecipient, userName string) error + SyncUserInfo(cmd CmdToSyncUserInfo) (uint, error) +} diff --git a/message/domain/subscribe.go b/message/domain/subscribe.go new file mode 100644 index 0000000..6a85f22 --- /dev/null +++ b/message/domain/subscribe.go @@ -0,0 +1,14 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package domain + +type MessageSubscribeAdapter interface { + GetAllSubsConfig(userName string) ([]MessageSubscribeDO, error) + GetSubsConfig(userName string) ([]MessageSubscribeDOWithPushConfig, int64, error) + SaveFilter(cmd CmdToGetSubscribe, userName string) error + AddSubsConfig(cmd CmdToAddSubscribe, userName string) ([]uint, error) + UpdateSubsConfig(cmd CmdToUpdateSubscribe, userName string) error + RemoveSubsConfig(cmd CmdToDeleteSubscribe, userName string) error +} diff --git a/message/infrastructure/dao.go b/message/infrastructure/dao.go new file mode 100644 index 0000000..cc94861 --- /dev/null +++ b/message/infrastructure/dao.go @@ -0,0 +1,238 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package infrastructure + +import ( + "time" + + "gorm.io/datatypes" +) + +type MessageListDAO struct { + Title string `gorm:"column:title" json:"title"` + Summary string `gorm:"column:summary" json:"summary"` + Source string `gorm:"column:source" json:"source"` + Type string `gorm:"column:type" json:"type"` + EventId string `gorm:"column:event_id" json:"event_id"` + DataContentType string `gorm:"column:data_content_type" json:"data_content_type"` + DataSchema string `gorm:"column:data_schema" json:"data_schema"` + SpecVersion string `gorm:"column:spec_version" json:"spec_version"` + EventTime time.Time `gorm:"column:time" json:"time"` + User string `gorm:"column:user" json:"user"` + SourceUrl string `gorm:"column:source_url" json:"source_url"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at" swaggerignore:"true"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" swaggerignore:"true"` + IsRead bool `gorm:"column:is_read" json:"is_read"` + SourceGroup string `gorm:"column:source_group" json:"source_group"` +} + +type MessagePushDAO struct { + SubscribeId int `gorm:"column:subscribe_id" json:"subscribe_id"` + RecipientId int64 `gorm:"column:recipient_id" json:"recipient_id"` + NeedMessage *bool `gorm:"column:need_message" json:"need_message"` + NeedPhone *bool `gorm:"column:need_phone" json:"need_phone"` + NeedMail *bool `gorm:"column:need_mail" json:"need_mail"` + NeedInnerMessage *bool `gorm:"column:need_inner_message" json:"need_inner_message"` + IsDeleted bool `gorm:"column:is_deleted" json:"is_deleted" swaggerignore:"true"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at" swaggerignore:"true"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" swaggerignore:"true"` +} + +type MessageRecipientDAO struct { + Id string `json:"id,omitempty"` + Name string `gorm:"column:recipient_name" json:"recipient_id"` + Mail string `gorm:"column:mail" json:"mail"` + Message string `gorm:"column:message" json:"message"` + Phone string `gorm:"column:phone" json:"phone"` + Remark string `gorm:"column:remark" json:"remark"` + UserName string `gorm:"column:user_id" json:"user_id"` + GiteeUserName string `gorm:"column:gitee_user_name" json:"gitee_user_name"` + IsDeleted bool `gorm:"column:is_deleted" json:"is_deleted"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at" swaggerignore:"true"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" swaggerignore:"true"` +} + +type MessageSubscribeDAO struct { + Id uint `gorm:"primaryKey;autoIncrement" json:"id,omitempty"` + Source string `gorm:"column:source" json:"source"` + EventType string `gorm:"column:event_type" json:"event_type"` + SpecVersion string `gorm:"column:spec_version" json:"spec_version"` + ModeName string `gorm:"column:mode_name" json:"mode_name"` + ModeFilter datatypes.JSON `gorm:"column:mode_filter" json:"mode_filter" swaggerignore:"true"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at" swaggerignore:"true"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" swaggerignore:"true"` + UserName string `gorm:"column:user_name" json:"user_name"` + IsDefault *bool `gorm:"column:is_default" json:"is_default"` + WebFilter datatypes.JSON `gorm:"column:web_filter" json:"web_filter" swaggerignore:"true"` +} + +type MessageSubscribeDAOWithPushConfig struct { + MessageSubscribeDAO + NeedMail *bool `gorm:"column:need_mail" json:"need_mail"` +} + +type CountDAO struct { + Source string `json:"source"` + Count int `json:"count"` +} + +type CmdToGetInnerMessageQuick struct { + Source string `json:"source"` + CountPerPage int `json:"count_per_page"` + PageNum int `json:"page"` + ModeName string `json:"mode_name"` +} + +type CmdToGetInnerMessage struct { + Source string `json:"source"` + EventType string `json:"event_type"` + IsRead string `json:"is_read"` + KeyWord string `json:"key_word"` + IsBot string `json:"is_bot"` + GiteeSigs string `json:"sig"` + Repos string `json:"repos"` + CountPerPage int `json:"count_per_page"` + PageNum int `json:"page"` + StartTime string `json:"start_time"` + EndTime string `json:"end_time"` + MySig string `json:"my_sig"` + OtherSig string `json:"other_sig_"` + MyManagement string `json:"my_management"` + OtherManagement string `json:"other_management"` + PrState string `json:"pr_state"` + PrCreator string `json:"pr_creator"` + PrAssignee string `json:"pr_assignee"` + IssueState string `json:"issue_state"` + IssueCreator string `json:"issue_creator"` + IssueAssignee string `json:"issue_assignee"` + NoteType string `json:"note_type"` + About string `json:"about"` + BuildStatus string `json:"build_status"` + BuildOwner string `json:"build_owner"` + BuildCreator string `json:"build_creator"` + BuildEnv string `json:"build_env"` + MeetingAction string `json:"meeting_action"` + MeetingSigGroup string `json:"meeting_sig"` + MeetingStartTime string `json:"meeting_start_time"` + MeetingEndTime string `json:"meeting_end_time"` + CVEComponent string `json:"cve_component"` + CVEState string `json:"cve_state"` + CVEAffected string `json:"cve_affected"` +} + +type CmdToSetIsRead struct { + Source string `json:"source"` + EventId string `json:"event_id"` +} + +type CmdToAddPushConfig struct { + SubscribeId int `json:"subscribe_id"` + RecipientId int64 `json:"recipient_id"` + NeedMessage bool `json:"need_message"` + NeedPhone bool `json:"need_phone"` + NeedMail bool `json:"need_mail"` + NeedInnerMessage bool `json:"need_inner_message"` +} + +type CmdToUpdatePushConfig struct { + SubscribeId []string `json:"subscribe_id"` + RecipientId string `json:"recipient_id"` + NeedMessage bool `json:"need_message"` + NeedPhone bool `json:"need_phone"` + NeedMail bool `json:"need_mail"` + NeedInnerMessage bool `json:"need_inner_message"` +} + +type CmdToDeletePushConfig struct { + SubscribeId int `json:"subscribe_id"` + RecipientId int64 `json:"recipient_id"` +} + +type CmdToAddRecipient struct { + Name string `json:"recipient_id"` + Mail string `json:"mail"` + Message string `json:"message"` + Phone string `json:"phone"` + Remark string `json:"remark"` +} + +type CmdToUpdateRecipient struct { + Id string `json:"id"` + Name string `json:"recipient_id"` + Mail string `json:"mail"` + Message string `json:"message"` + Phone string `json:"phone"` + Remark string `json:"remark"` +} + +type CmdToDeleteRecipient struct { + RecipientId string `json:"recipient_id"` +} + +type CmdToSyncUserInfo struct { + Mail string `json:"mail"` + Phone string `json:"phone"` + CountryCode string `json:"country_code"` + UserName string `json:"user_name"` + GiteeUserName string `json:"gitee_user_name"` +} + +type CmdToGetSubscribe struct { + Source string `json:"source,omitempty"` + EventType string `json:"event_type,omitempty"` + IsRead string `json:"is_read,omitempty"` + KeyWord string `json:"key_word,omitempty"` + IsBot string `json:"is_bot,omitempty"` + GiteeSigs string `json:"sig,omitempty"` + Repos string `json:"repos,omitempty"` + CountPerPage int `json:"count_per_page,omitempty"` + PageNum int `json:"page,omitempty"` + StartTime string `json:"start_time,omitempty"` + EndTime string `json:"end_time,omitempty"` + MySig string `json:"my_sig,omitempty"` + OtherSig string `json:"other_sig,omitempty"` + MyManagement string `json:"my_management,omitempty"` + OtherManagement string `json:"other_management,omitempty"` + PrState string `json:"pr_state,omitempty"` + PrCreator string `json:"pr_creator,omitempty"` + PrAssignee string `json:"pr_assignee,omitempty"` + IssueState string `json:"issue_state,omitempty"` + IssueCreator string `json:"issue_creator,omitempty"` + IssueAssignee string `json:"issue_assignee,omitempty"` + NoteType string `json:"note_type,omitempty"` + About string `json:"about,omitempty"` + BuildStatus string `json:"build_status,omitempty"` + BuildOwner string `json:"build_owner,omitempty"` + BuildCreator string `json:"build_creator,omitempty"` + BuildEnv string `json:"build_env,omitempty"` + MeetingAction string `json:"meeting_action,omitempty"` + MeetingSigGroup string `json:"meeting_sig,omitempty"` + MeetingStartTime string `json:"meeting_start_time,omitempty"` + MeetingEndTime string `json:"meeting_end_time,omitempty"` + CVEComponent string `json:"cve_component,omitempty"` + CVEState string `json:"cve_state,omitempty"` + CVEAffected string `json:"cve_affected,omitempty"` + SpecVersion string `json:"spec_version,omitempty"` + ModeName string `json:"mode_name,omitempty"` +} + +type CmdToAddSubscribe struct { + Source string `json:"source"` + EventType string `json:"event_type"` + SpecVersion string `json:"spec_version"` + ModeFilter datatypes.JSON `json:"mode_filter" swaggerignore:"true"` + ModeName string `json:"mode_name"` +} + +type CmdToUpdateSubscribe struct { + Source string `json:"source"` + OldName string `json:"old_name"` + NewName string `json:"new_name"` +} + +type CmdToDeleteSubscribe struct { + Source string `json:"source"` + ModeName string `json:"mode_name"` +} diff --git a/message/infrastructure/message.go b/message/infrastructure/message.go new file mode 100644 index 0000000..33dfa9a --- /dev/null +++ b/message/infrastructure/message.go @@ -0,0 +1,463 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package infrastructure + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + "time" + + "github.com/sirupsen/logrus" + "golang.org/x/xerrors" + "gorm.io/gorm" + + "github.com/opensourceways/message-manager/common/postgresql" + "github.com/opensourceways/message-manager/utils" +) + +func MessageListAdapter() *messageAdapter { + return &messageAdapter{} +} + +type messageAdapter struct{} + +// 单值过滤 +func applySingleValueFilter(query *gorm.DB, column string, value string) *gorm.DB { + if value != "" { + query = query.Where(column+" = ?", value) + } + return query +} + +// 关键字过滤 +func applyKeyWordFilter(query *gorm.DB, field string, keyWord string) *gorm.DB { + if keyWord != "" { + query = query.Where(field+" ILIKE ?", "%"+keyWord+"%") + } + return query +} + +// 仓库过滤 +func applyRepoFilter(query *gorm.DB, myManagement string, repos string) *gorm.DB { + var lRepo []string + if myManagement != "" { + lRepo, _ = utils.GetUserAdminRepos(myManagement) + } + if repos != "" { + lRepo = append(lRepo, strings.Split(repos, ",")...) + } + if len(lRepo) != 0 { + query = query.Where("cloud_event_message.source_group = ANY(?)", fmt.Sprintf("{%s}", + strings.Join(lRepo, ","))) + } + return query +} + +// 时间过滤 +func applyTimeFilter(query *gorm.DB, startTime string, endTime string) *gorm.DB { + start := utils.ParseUnixTimestamp(startTime) + end := utils.ParseUnixTimestamp(endTime) + if start != nil && end != nil { + query = query.Where("cloud_event_message.time BETWEEN ? AND ?", *start, *end) + } else if start != nil { + query = query.Where("cloud_event_message.time >= ?", *start) + } else if end != nil { + query = query.Where("cloud_event_message.time <= ?", *end) + } + return query +} + +// 处理机器人过滤条件 +func applyBotFilter(query *gorm.DB, isBot string, eventType string) *gorm.DB { + botNames := []string{"ci-robot", "openeuler-ci-bot", "openeuler-sync-bot"} + condition := func(event string) string { + return fmt.Sprintf(`jsonb_extract_path_text(cloud_event_message.data_json, +'%sEvent', 'Sender', 'Name')`, event) + } + + generateConditions := func(operator string) string { + var suffix string + if operator == "=" { + suffix = " ANY(%s)" + } else { + suffix = " ALL(?)" + } + conditions := []string{ + condition("Issue") + " " + operator + suffix, + condition("PullRequest") + " " + operator + suffix, + condition("Note") + " " + operator + suffix, + } + return strings.Join(conditions, " OR ") + } + defaultSuffix := fmt.Sprintf("{%s}", strings.Join(botNames, ",")) + + var event string + if eventType == "pr" { + event = "PullRequest" + } else if eventType == "issue" { + event = "Issue" + } else { + event = "Note" + } + + if isBot == "true" { + if eventType != "" { + query = query.Where(condition(event)+" = ANY(?)", defaultSuffix) + } else { + query = query.Where(generateConditions("="), + defaultSuffix, defaultSuffix, defaultSuffix) + } + } else if isBot == "false" { + if eventType != "" { + query = query.Where(condition(event)+" <> ALL(?)", defaultSuffix) + } else { + query = query.Where(generateConditions("<>"), + defaultSuffix, defaultSuffix, defaultSuffix) + } + } + + return query +} + +// sig组过滤 +func applySigGroupFilter(query *gorm.DB, mySig string, giteeSigs string) *gorm.DB { + var lSig []string + // 获取我的sig组 + if mySig != "" { + sigs, err := utils.GetUserSigInfo(mySig) + if err == nil { + lSig = append(lSig, sigs...) + } + } + // 添加 Gitee 仓库所属sig + if giteeSigs != "" { + lSig = append(lSig, strings.Split(giteeSigs, ",")...) + } + // 如果有sig,则添加过滤条件 + if len(lSig) > 0 { + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, "+ + "'SigGroupName') = ANY(?)", + fmt.Sprintf("{%s}", strings.Join(lSig, ","))) + } + + return query +} + +func applyPrAssigneeFilter(query *gorm.DB, assignee string) *gorm.DB { + if assignee != "" { + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, "+ + "'Assignees') ILIKE ?", "%"+assignee+"%") + } + return query +} + +// 复合过滤,处理PullRequest和Issue +func applyCompositeFilters(query *gorm.DB, eventType string, state string, creator string, + assignee string) *gorm.DB { + if eventType == "IssueEvent" { + query = applySingleValueFilter(query, fmt.Sprintf("jsonb_extract_path_text("+ + "cloud_event_message.data_json, '%s', 'Issue', 'State')", eventType), state) + query = applySingleValueFilter(query, fmt.Sprintf("jsonb_extract_path_text("+ + "cloud_event_message.data_json, '%s', 'Assignee', 'Login')", eventType), assignee) + } else if eventType == "PullRequestEvent" { + query = applySingleValueFilter(query, fmt.Sprintf("jsonb_extract_path_text("+ + "cloud_event_message.data_json, '%s', 'State')", eventType), state) + query = applyPrAssigneeFilter(query, assignee) + } + query = applySingleValueFilter(query, fmt.Sprintf("jsonb_extract_path_text("+ + "cloud_event_message.data_json, '%s', 'User', 'Login')", eventType), creator) + + return query +} + +// @某人消息过滤 +func applyAboutFilter(query *gorm.DB, about string) *gorm.DB { + if about != "" { + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, 'NoteEvent', "+ + "'Comment', 'Body') LIKE ?", "%"+about+"%") + } + return query +} + +// Build相关过滤 +func applyBuildFilters(query *gorm.DB, buildStatus string, buildOwner string, + buildCreator string, buildEnv string) *gorm.DB { + query = applySingleValueFilter(query, "jsonb_extract_path_text(cloud_event_message.data_json,"+ + " 'Body', 'Status')", buildStatus) + query = applySingleValueFilter(query, "cloud_event_message.user", buildOwner) + query = applySingleValueFilter(query, "jsonb_extract_path_text(cloud_event_message.data_json,"+ + " 'Body', 'User')", buildCreator) + query = applySingleValueFilter(query, "jsonb_extract_path_text(cloud_event_message.data_json,"+ + " 'Body', 'Chroot')", buildEnv) + return query +} + +// 会议相关过滤 +func applyMeetingFilters(query *gorm.DB, meetingAction string, meetingSigGroup string, + meetingStartTime string) *gorm.DB { + query = applySingleValueFilter(query, "jsonb_extract_path_text(cloud_event_message.data_json,"+ + " 'Action')", meetingAction) + query = applySingleValueFilter(query, "jsonb_extract_path_text(cloud_event_message.data_json,"+ + " 'Msg', 'GroupName')", meetingSigGroup) + + if meetingStartTime != "" { + start := utils.ParseUnixTimestamp(meetingStartTime) + if start != nil { + logrus.Infof("the time is %v, the time is %v, the date is %v", meetingStartTime, + start.Format(time.DateTime), start.Format(time.DateOnly)) + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json,"+ + " 'Msg', 'Date') = ?", start.Format(time.DateOnly)) + } + } + return query +} + +// CVE相关过滤 +func applyCVEFilters(query *gorm.DB, cveComponent string, cveState string, cveAffected string) *gorm.DB { + if cveComponent != "" { + lComponent := strings.Split(cveComponent, ",") + var sql []string + for _, comp := range lComponent { + sql = append(sql, "%"+comp+"%") + } + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, "+ + "'CVEComponent') LIKE ANY (?)", fmt.Sprintf("{%s}", strings.Join(sql, ","))) + } + + if cveState != "" { + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, 'IssueEvent',"+ + " 'Issue', 'State') = ANY (?)", fmt.Sprintf("{%s}", cveState)) + } + + if cveAffected != "" { + lAffected := strings.Split(cveAffected, ",") + var sql []string + for _, affect := range lAffected { + sql = append(sql, "%"+affect+"%") + } + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, "+ + "'CVEAffectVersion') ILIKE ANY (?)", fmt.Sprintf("{%s}", strings.Join(sql, ","))) + } + return query +} + +func GenQuery(query *gorm.DB, params CmdToGetInnerMessage) *gorm.DB { + // 简单过滤 + query = applySingleValueFilter(query, "inner_message.source", params.Source) + query = applySingleValueFilter(query, "is_read", params.IsRead) + query = applySingleValueFilter(query, "cloud_event_message.type", params.EventType) + query = applySingleValueFilter(query, "jsonb_extract_path_text(cloud_event_message."+ + "data_json, 'NoteEvent', 'NoteableType')", params.NoteType) + query = applyKeyWordFilter(query, "cloud_event_message.source_group", params.KeyWord) + query = applyRepoFilter(query, params.MyManagement, params.Repos) + query = applyTimeFilter(query, params.StartTime, params.EndTime) + + // 复杂过滤 + query = applyBotFilter(query, params.IsBot, params.EventType) + query = applySigGroupFilter(query, params.MySig, params.GiteeSigs) + query = applyCompositeFilters(query, "PullRequestEvent", params.PrState, params.PrCreator, + params.PrAssignee) + query = applyCompositeFilters(query, "IssueEvent", params.IssueState, params.IssueCreator, + params.IssueAssignee) + query = applyAboutFilter(query, params.About) + query = applyBuildFilters(query, params.BuildStatus, params.BuildOwner, params.BuildCreator, + params.BuildEnv) + query = applyMeetingFilters(query, params.MeetingAction, params.MeetingSigGroup, + params.MeetingStartTime) + query = applyCVEFilters(query, params.CVEComponent, params.CVEState, params.CVEAffected) + return query +} + +func GenQueryQuick(query *gorm.DB, data MessageSubscribeDAO) *gorm.DB { + var modeFilterMap map[string]interface{} + err := json.Unmarshal(data.ModeFilter, &modeFilterMap) + if err != nil { + logrus.Errorf("unmarshal modefilter failed, err:%v", err) + return query + } + if data.Source != "" { + query = query.Where("inner_message.source = ?", data.Source) + } + for k, v := range modeFilterMap { + splitK := strings.Split(k, ".") + vString, ok := v.(string) + if !ok { + logrus.Errorf("it's not ok for type string") + break + } + queryString := generateJSONBExtractPath(splitK) + + if strings.Contains(k, "Sender.Name") { + if strings.Contains(v.(string), "ne=") { + vString = strings.ReplaceAll(vString, "ne=", "") + vString = strings.Join(strings.Split(vString, " "), ",") + + query = query.Where(queryString+" <> ALL(?)", fmt.Sprintf("{%s}", vString)) + } else { + vString = strings.ReplaceAll(vString, "oneof=", "") + query = query.Where(queryString+" = ANY(?)", fmt.Sprintf("{%s}", vString)) + } + } else if strings.Contains(k, "NoteEvent.Comment.Body") { + vString = strings.ReplaceAll(vString, "contains=", "") + query = query.Where(queryString+" LIKE ?", "%"+vString+"%") + } else if strings.Contains(k, "MeetingStartTime") { + // 使用正则表达式提取时间 + re := regexp.MustCompile(`gt=(.*?),lt=(.*?)$`) + matches := re.FindStringSubmatch(vString) + query = query. + Where("jsonb_extract_path_text(cloud_event_message."+ + "data_json,'MeetingStartTime') BETWEEN ? AND ?", matches[1], matches[2]) + } else if strings.Contains(k, "CVEAffectVersion") { + vString = strings.ReplaceAll(vString, "contains=", "") + lString := strings.Split(vString, " ") + var newLString []string + for _, s := range lString { + newLString = append(newLString, "%"+s+"%") + } + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, "+ + "'CVEAffectVersion') ILIKE ANY(?)", + fmt.Sprintf("{%s}", strings.Join(newLString, ","))) + } else if strings.Contains(k, "Assignees") { + vString = strings.ReplaceAll(vString, "contains=", "") + query = query.Where("jsonb_extract_path_text(cloud_event_message.data_json, "+ + "'Assignees') ILIKE ?", "%"+vString+"%") + } else { + if vString != "" { + vString = strings.ReplaceAll(vString, "oneof=", "") + vString = strings.ReplaceAll(vString, "eq=", "") + query = query.Where(queryString+" = ANY(?)", fmt.Sprintf("{%s}", vString)) + } + } + } + return query +} + +func generateJSONBExtractPath(fields []string) string { + var sb strings.Builder + sb.WriteString("jsonb_extract_path_text(cloud_event_message.data_json") + + for range fields { + sb.WriteString(", '%s'") + } + + sb.WriteString(")") + + formatArgs := make([]interface{}, len(fields)) + for i := range fields { + formatArgs[i] = fields[i] + } + + return fmt.Sprintf(sb.String(), formatArgs...) +} + +func (s *messageAdapter) GetInnerMessageQuick(cmd CmdToGetInnerMessageQuick, + userName string) ([]MessageListDAO, int64, error) { + var data []MessageSubscribeDAO + if result := postgresql.DB().Table("message_center.subscribe_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where("user_name = ? OR user_name IS NULL", userName). + Where("source = ? AND mode_name = ?", cmd.Source, cmd.ModeName). + Scan(&data); result.Error != nil { + return []MessageListDAO{}, 0, xerrors.Errorf("查询失败, err:%v", result.Error) + } + + query := postgresql.DB().Table("message_center.inner_message"). + Joins("JOIN message_center.cloud_event_message ON "+ + "inner_message.event_id = cloud_event_message.event_id"). + Joins("JOIN message_center.recipient_config ON "+ + "inner_message.recipient_id = recipient_config.id"). + Where("inner_message.is_deleted = ? AND recipient_config.is_deleted = ?", false, false). + Where("recipient_config.user_id = ?", userName) + + offsetNum := (cmd.PageNum - 1) * cmd.CountPerPage + GenQueryQuick(query, data[0]) + if len(data) != 0 { + var lType []string + for _, dt := range data { + lType = append(lType, dt.EventType) + } + query = query.Where("cloud_event_message.type = ANY(?)", fmt.Sprintf("{%s}", + strings.Join(lType, ","))) + } + var Count int64 + query.Count(&Count) + + var response []MessageListDAO + if result := query.Limit(cmd.CountPerPage).Offset(offsetNum). + Order("cloud_event_message.time DESC"). + Scan(&response); result.Error != nil { + return []MessageListDAO{}, 0, xerrors.Errorf("get inner message failed, err:%v", + result.Error) + } + return response, Count, nil +} + +func (s *messageAdapter) GetInnerMessage(cmd CmdToGetInnerMessage, + userName string) ([]MessageListDAO, int64, error) { + query := postgresql.DB().Table("message_center.inner_message"). + Joins("JOIN message_center.cloud_event_message ON "+ + "inner_message.event_id = cloud_event_message.event_id"). + Joins("JOIN message_center.recipient_config ON "+ + "inner_message.recipient_id = recipient_config.id"). + Where("inner_message.is_deleted = ? AND recipient_config.is_deleted = ?", false, false). + Where("recipient_config.user_id = ?", userName) + + GenQuery(query, cmd) + + var Count int64 + query.Count(&Count) + + var response []MessageListDAO + offsetNum := (cmd.PageNum - 1) * cmd.CountPerPage + if result := query.Debug().Limit(cmd.CountPerPage).Offset(offsetNum). + Order("cloud_event_message.time DESC"). + Scan(&response); result.Error != nil { + logrus.Errorf("get inner message failed, err:%v", result.Error.Error()) + return []MessageListDAO{}, 0, xerrors.Errorf("查询失败, err:%v", + result.Error) + } + + return response, Count, nil +} + +func (s *messageAdapter) CountAllUnReadMessage(userName string) ([]CountDAO, error) { + var CountData []CountDAO + sqlCount := `SELECT inner_message.source, COUNT(*) FROM message_center.inner_message + JOIN message_center.cloud_event_message ON inner_message.event_id = cloud_event_message.event_id + AND inner_message.source = cloud_event_message.source + JOIN message_center.recipient_config ON + cast(inner_message.recipient_id AS BIGINT) = recipient_config.id + WHERE is_read = ? AND recipient_config.user_id = ? + AND inner_message.is_deleted = ? + AND recipient_config.is_deleted = ? + GROUP BY inner_message.source` + if result := postgresql.DB().Raw(sqlCount, false, userName, false, false). + Scan(&CountData); result.Error != nil { + return []CountDAO{}, xerrors.Errorf("get count failed, err:%v", result.Error) + } + return CountData, nil +} + +func (s *messageAdapter) SetMessageIsRead(source, eventId string) error { + if result := postgresql.DB().Table("message_center.inner_message"). + Where("inner_message.source = ? AND inner_message.event_id = ?", source, + eventId).Where("inner_message.is_deleted = ?", false). + Update("is_read", true); result.Error != nil { + return xerrors.Errorf("set message is_read failed, err:%v", result.Error.Error()) + } + return nil +} + +func (s *messageAdapter) RemoveMessage(source, eventId string) error { + if result := postgresql.DB().Table("message_center.inner_message"). + Where("inner_message.source = ? AND inner_message."+ + "event_id = ?", source, eventId). + Update("is_deleted", true); result.Error != nil { + return xerrors.Errorf("remove inner message failed, err:%v", result.Error.Error()) + } + return nil +} diff --git a/message/infrastructure/push.go b/message/infrastructure/push.go new file mode 100644 index 0000000..0900b0b --- /dev/null +++ b/message/infrastructure/push.go @@ -0,0 +1,94 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package infrastructure + +import ( + "time" + + "golang.org/x/xerrors" + "gorm.io/gorm" + + "github.com/opensourceways/message-manager/common/postgresql" +) + +func MessagePushAdapter() *messagePushAdapter { + return &messagePushAdapter{} +} + +type messagePushAdapter struct{} + +func (s *messagePushAdapter) GetPushConfig(subsIds []string, countPerPage, pageNum int, + userName string) ([]MessagePushDAO, error) { + + query := postgresql.DB().Table("message_center.push_config"). + Where(gorm.Expr("push_config.is_deleted = ?", false)) + offsetNum := (pageNum - 1) * countPerPage + query = query.Select("push_config.*"). + Joins("JOIN message_center.recipient_config ON recipient_config.id = push_config."+ + "recipient_id"). + Where(gorm.Expr("recipient_config.is_deleted = ?", false)). + Where("recipient_config.user_id = ?", userName) + + var response []MessagePushDAO + if result := query.Limit(countPerPage).Offset(offsetNum). + Find(&response, "subscribe_id IN ?", subsIds); result.Error != nil { + return []MessagePushDAO{}, xerrors.Errorf("查询失败") + } + + return response, nil +} + +func (s *messagePushAdapter) AddPushConfig(cmd CmdToAddPushConfig) error { + + var existData MessagePushDAO + if result := postgresql.DB().Table("message_center.push_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where("subscribe_id = ? AND recipient_id = ?", cmd.SubscribeId, cmd.RecipientId). + Scan(&existData); result.RowsAffected != 0 { + return xerrors.Errorf("新增配置失败,配置已存在") + } + + if result := postgresql.DB().Table("message_center.push_config"). + Create(MessagePushDAO{ + SubscribeId: cmd.SubscribeId, + RecipientId: cmd.RecipientId, + NeedMessage: &cmd.NeedMessage, + NeedPhone: &cmd.NeedPhone, + NeedMail: &cmd.NeedMail, + NeedInnerMessage: &cmd.NeedInnerMessage, + IsDeleted: false, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }); result.Error != nil { + return xerrors.Errorf("新增配置失败,err:%v", result.Error) + } + return nil +} + +func (s *messagePushAdapter) UpdatePushConfig(cmd CmdToUpdatePushConfig) error { + if result := postgresql.DB().Table("message_center.push_config").Debug(). + Where("is_deleted = ?", false). + Where("subscribe_id IN ? AND recipient_id = ?", cmd.SubscribeId, cmd.RecipientId). + Updates(&MessagePushDAO{ + NeedMessage: &cmd.NeedMessage, + NeedPhone: &cmd.NeedPhone, + NeedMail: &cmd.NeedMail, + NeedInnerMessage: &cmd.NeedInnerMessage, + UpdatedAt: time.Now(), + }); result.Error != nil { + return xerrors.Errorf("更新配置失败,err:%v", result.Error) + } + return nil +} + +func (s *messagePushAdapter) RemovePushConfig(cmd CmdToDeletePushConfig) error { + if result := postgresql.DB().Table("message_center.push_config"). + Where(gorm.Expr("is_deleted IS NULL OR is_deleted = ?", false)). + Where("subscribe_id = ? AND recipient_id = ?", cmd.SubscribeId, cmd.RecipientId). + Update("is_deleted", true); result.Error != nil { + return xerrors.Errorf("删除配置失败,err:%v", result.Error) + } + return nil +} diff --git a/message/infrastructure/recipient.go b/message/infrastructure/recipient.go new file mode 100644 index 0000000..e142b1b --- /dev/null +++ b/message/infrastructure/recipient.go @@ -0,0 +1,241 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package infrastructure + +import ( + "fmt" + "time" + + "github.com/opensourceways/message-manager/utils" + "github.com/sirupsen/logrus" + "golang.org/x/xerrors" + "gorm.io/datatypes" + "gorm.io/gorm" + + "github.com/opensourceways/message-manager/common/postgresql" +) + +func MessageRecipientAdapter() *messageRecipientAdapter { + return &messageRecipientAdapter{} +} + +type messageRecipientAdapter struct{} + +type RecipientController struct { + Name string `gorm:"column:recipient_name" json:"recipient_id"` + Mail string `gorm:"column:mail" json:"mail"` + Message string `gorm:"column:message" json:"message"` + Phone string `gorm:"column:phone" json:"phone"` + Remark string `gorm:"column:remark" json:"remark"` + UserName string `gorm:"column:user_id" json:"user_id"` + GiteeUserName string `gorm:"column:gitee_user_name" json:"gitee_user_name"` + IsDeleted bool `gorm:"column:is_deleted" json:"is_deleted"` + CreatedAt time.Time `gorm:"column:created_at" json:"created_at" swaggerignore:"true"` + UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at" swaggerignore:"true"` +} + +func getTable() *gorm.DB { + return postgresql.DB().Table("message_center.recipient_config"). + Where(gorm.Expr("is_deleted = ?", false)) +} + +func (ctl *messageRecipientAdapter) GetRecipientConfig(countPerPage, pageNum int, userName string) ( + []MessageRecipientDAO, int64, error) { + var response []MessageRecipientDAO + var Count int64 + getTable().Where("user_id = ?", userName).Count(&Count) + + offsetNum := (pageNum - 1) * countPerPage + + if result := getTable().Where("user_id = ?", userName).Limit(countPerPage).Offset(offsetNum). + Order("recipient_config.created_at DESC"). + Find(&response); result.Error != nil { + return []MessageRecipientDAO{}, 0, xerrors.Errorf("get recipient config failed, err:%v", result.Error.Error()) + } + return response, Count, nil +} + +func (ctl *messageRecipientAdapter) AddRecipientConfig(cmd CmdToAddRecipient, + userName string) error { + var existData MessageRecipientDAO + if result := getTable().Where("recipient_name = ? AND user_id = ?", cmd.Name, userName). + Scan(&existData); result.RowsAffected != 0 { + return xerrors.Errorf("接收人姓名不能相同") + } + + if result := getTable().Create(MessageRecipientDAO{ + Name: cmd.Name, + Mail: cmd.Mail, + Message: cmd.Message, + Phone: cmd.Phone, + Remark: cmd.Remark, + UserName: userName, + IsDeleted: false, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }); result.Error != nil { + return xerrors.Errorf("add new recipient failed, err:%v", result.Error) + } + return nil +} + +func (ctl *messageRecipientAdapter) UpdateRecipientConfig(cmd CmdToUpdateRecipient, + userName string) error { + if result := getTable().Where("id = ? AND user_id = ?", cmd.Id, userName). + Updates(RecipientController{ + Name: cmd.Name, + Mail: cmd.Mail, + Message: cmd.Message, + Phone: cmd.Phone, + Remark: cmd.Remark, + UpdatedAt: time.Now(), + }); result.Error != nil { + return xerrors.Errorf("update recipient config failed, err:%v", result.Error) + } + return nil +} + +func (ctl *messageRecipientAdapter) RemoveRecipientConfig(cmd CmdToDeleteRecipient, + userName string) error { + if result := getTable().Where("id = ? AND user_id = ?", cmd.RecipientId, userName). + Update("is_deleted", true); result.Error != nil || result.RowsAffected == 0 { + return xerrors.Errorf("删除配置失败, err:%v", result.Error) + } + return nil +} + +func (ctl *messageRecipientAdapter) SyncUserInfo(cmd CmdToSyncUserInfo) (uint, error) { + var oldInfo RecipientController + + if result := getTable(). + Where("user_id = ?", cmd.UserName). + Scan(&oldInfo); result.RowsAffected != 0 { + newInfo := &oldInfo + newInfo.Mail = cmd.Mail + newInfo.Message = cmd.CountryCode + cmd.Phone + newInfo.Phone = cmd.CountryCode + cmd.Phone + newInfo.UserName = cmd.UserName + newInfo.GiteeUserName = cmd.GiteeUserName + getTable().Where("user_id = ?", cmd.UserName).Save(&newInfo) + } else { + newInfo := RecipientController{ + Mail: cmd.Mail, + Message: cmd.CountryCode + cmd.Phone, + Phone: cmd.CountryCode + cmd.Phone, + UserName: cmd.UserName, + GiteeUserName: cmd.GiteeUserName, + } + getTable().Create(&newInfo) + } + + var id uint + getTable().Where(gorm.Expr("is_deleted = ?", false)). + Where("user_id = ?", cmd.UserName).Select("id").Scan(&id) + + subscribeDefault(id, cmd.UserName, cmd.GiteeUserName) + + return id, nil +} + +func getDefaultFilter(giteeUserName string) ([]MessageSubscribeDAO, error) { + defaultFilter := []MessageSubscribeDAO{ + {Source: utils.GiteeSource, EventType: "issue", SpecVersion: "1.0", ModeName: "指派给我的issue", + ModeFilter: datatypes.JSON( + fmt.Sprintf(`{"IssueEvent.Assignee.Login": "eq=%s"}`, giteeUserName)), + WebFilter: datatypes.JSON(fmt.Sprintf( + `{"issue_assignee": "%s", "event_type": "issue"}`, giteeUserName))}, + {Source: utils.GiteeSource, EventType: "pr", SpecVersion: "1.0", ModeName: "待我处理的pr", + ModeFilter: datatypes.JSON(fmt.Sprintf( + `{"Assignees: "contains=%s"}`, giteeUserName)), + WebFilter: datatypes.JSON(fmt.Sprintf( + `{"pr_assignee": "%s", "event_type": "pr"}`, giteeUserName))}, + {Source: utils.GiteeSource, EventType: "note", SpecVersion: "1.0", ModeName: "我提的issue的评论", + ModeFilter: datatypes.JSON( + fmt.Sprintf(`{"NoteEvent.Issue.User.Login": "eq=%s"}`, giteeUserName)), + WebFilter: datatypes.JSON(fmt.Sprintf(`{"note_type": "Issue", "event_type": "note"}`))}, + } + return defaultFilter, nil +} + +func addPushConfig(subsId int, recipientId int64) error { + needMessage, needPhone, needMail, needInnerMessage := new(bool), new(bool), new(bool), new(bool) + *needMessage = false + *needPhone = false + *needMail = false + *needInnerMessage = true + + var existData MessagePushDAO + if result := postgresql.DB().Table("message_center.push_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where("subscribe_id = ? AND recipient_id = ?", subsId, recipientId). + Scan(&existData); result.RowsAffected != 0 { + logrus.Errorf("the exist data is %v", existData) + return nil + } + + if result := postgresql.DB().Table("message_center.push_config"). + Create(MessagePushDAO{ + SubscribeId: subsId, + RecipientId: recipientId, + NeedMessage: needMessage, + NeedPhone: needPhone, + NeedMail: needMail, + NeedInnerMessage: needInnerMessage, + IsDeleted: false, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }); result.Error != nil { + return xerrors.Errorf("add push config failed, err:%v", result.Error) + } + return nil +} + +func subscribeDefault(recipientId uint, userName string, giteeUserName string) { + if userName == "" || giteeUserName == "" { + logrus.Errorf("username is empty or gitee username is empty") + return + } + defaultFilter, err := getDefaultFilter(giteeUserName) + if err != nil { + logrus.Errorf("get default filter failed, err:%v", err) + return + } + + for _, subs := range defaultFilter { + var existData MessageSubscribeDAO + if result := postgresql.DB().Table("message_center.subscribe_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where("source = ? AND mode_name = ?", subs.Source, subs.ModeName). + Where("user_name = ?", userName). + Scan(&existData); result.RowsAffected != 0 { + continue + } + + isDefault := true + newSubsConfig := MessageSubscribeDAO{ + Source: subs.Source, + EventType: subs.EventType, + SpecVersion: subs.SpecVersion, + ModeFilter: subs.ModeFilter, + WebFilter: subs.WebFilter, + ModeName: subs.ModeName, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + UserName: userName, + IsDefault: &isDefault, + } + if result := postgresql.DB().Table("message_center.subscribe_config"). + Create(&newSubsConfig); result.Error != nil { + logrus.Errorf("create subs failed, err:%v", result.Error) + break + } + + err = addPushConfig(int(newSubsConfig.Id), int64(recipientId)) + if err != nil { + logrus.Errorf("add push config failed, err:%v", err) + return + } + } +} diff --git a/message/infrastructure/subscribe.go b/message/infrastructure/subscribe.go new file mode 100644 index 0000000..82ce3de --- /dev/null +++ b/message/infrastructure/subscribe.go @@ -0,0 +1,199 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package infrastructure + +import ( + "encoding/json" + "log" + "strings" + "time" + + "github.com/sirupsen/logrus" + "golang.org/x/xerrors" + "gorm.io/datatypes" + "gorm.io/gorm" + + "github.com/opensourceways/message-manager/common/postgresql" +) + +func MessageSubscribeAdapter() *messageSubscribeAdapter { + return &messageSubscribeAdapter{} +} + +type messageSubscribeAdapter struct{} + +func (ctl *messageSubscribeAdapter) GetAllSubsConfig(userName string) ([]MessageSubscribeDAO, error) { + + var response []MessageSubscribeDAO + query := postgresql.DB().Table("message_center.subscribe_config"). + Where("user_name = ? OR user_name IS NULL", userName). + Where(gorm.Expr("subscribe_config.is_deleted = ?", false)) + + if result := query.Order("subscribe_config.id").Find(&response); result.Error != nil { + return []MessageSubscribeDAO{}, xerrors.Errorf("查询失败") + } + + return response, nil +} + +func (ctl *messageSubscribeAdapter) GetSubsConfig(userName string) ([]MessageSubscribeDAOWithPushConfig, int64, error) { + var response []MessageSubscribeDAOWithPushConfig + + query := postgresql.DB().Table("message_center.subscribe_config"). + Select("subscribe_config.*, (SELECT push_config.need_mail FROM message_center."+ + "push_config WHERE push_config.subscribe_id = subscribe_config.id LIMIT 1) AS need_mail"). + Where("subscribe_config.is_deleted = ? AND subscribe_config.user_name = ?", + false, userName) + + var Count int64 + query.Count(&Count) + if result := query.Debug().Order("subscribe_config.id").Find(&response); result.Error != nil { + return []MessageSubscribeDAOWithPushConfig{}, 0, xerrors.Errorf("查询失败") + } + + return response, Count, nil +} + +func getRecipientId(userName string) *uint { + var id uint + result := postgresql.DB().Table("message_center.recipient_config"). + Select("id"). + Where("is_deleted = ?", false). + Where("user_id = ?", userName). + Scan(&id) + if result.Error != nil { + // 处理错误 + log.Println("Error fetching id:", result.Error) + return nil + } + return &id +} + +func (ctl *messageSubscribeAdapter) SaveFilter(cmd CmdToGetSubscribe, userName string) error { + if userName == "" { + return xerrors.Errorf("用户名为空") + } + if cmd.ModeName == "" { + return xerrors.Errorf("规则名为空") + } + + var existData MessageSubscribeDAO + + if result := postgresql.DB().Table("message_center.subscribe_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where("source = ? AND mode_name = ?", cmd.Source, cmd.ModeName). + Where("user_name = ?", userName). + Scan(&existData); result.RowsAffected != 0 { + return xerrors.Errorf("保存配置失败") + } + lType := strings.Split(cmd.EventType, ",") + for _, et := range lType { + var modeFilter datatypes.JSON + modeFilter, _ = TransToDbFormat(cmd.Source, et, cmd) + if len(modeFilter) == 0 { + return xerrors.Errorf("no modeFilter exist") + } + isDefault := new(bool) + *isDefault = false + jsonFilter, err := json.Marshal(cmd) + if err != nil { + return xerrors.Errorf("marshal data failed, err:%v", err) + } + + subs := MessageSubscribeDAO{ + Source: cmd.Source, + EventType: et, + SpecVersion: cmd.SpecVersion, + ModeFilter: modeFilter, + ModeName: cmd.ModeName, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + UserName: userName, + IsDefault: isDefault, + WebFilter: jsonFilter, + } + + result := postgresql.DB().Table("message_center.subscribe_config"). + Create(&subs) + if result.Error != nil { + return xerrors.Errorf("保存配置失败") + } + + recipientId := getRecipientId(userName) + if recipientId == nil { + return nil + } + err = addPushConfig(int(subs.Id), int64(*recipientId)) + if err != nil { + return xerrors.Errorf("订阅失败,err:%v", err) + } + } + return nil +} + +func (ctl *messageSubscribeAdapter) AddSubsConfig(cmd CmdToAddSubscribe, userName string) ([]uint, error) { + var existData MessageSubscribeDAO + + if result := postgresql.DB().Table("message_center.subscribe_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where("source = ? AND mode_name = ?", cmd.Source, cmd.ModeName). + Where("user_name = ?", userName). + Scan(&existData); result.RowsAffected != 0 { + return []uint{}, xerrors.Errorf("新增配置失败") + } + + var subscribeIds []uint + lType := strings.Split(cmd.EventType, ",") + for _, et := range lType { + result := postgresql.DB().Table("message_center.subscribe_config"). + Create(MessageSubscribeDAO{ + Source: cmd.Source, + EventType: et, + SpecVersion: cmd.SpecVersion, + ModeName: cmd.ModeName, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + UserName: userName, + }) + if result.Error != nil { + return []uint{}, xerrors.Errorf("新增配置失败") + } + + var id uint + postgresql.DB().Table("message_center.subscribe_config"). + Debug(). + Where(gorm.Expr("is_deleted = ?", false)). + Where("source = ? AND event_type = ? AND mode_name = ? AND user_name = ?", + cmd.Source, et, cmd.ModeName, userName).Select("id").Scan(&id) + subscribeIds = append(subscribeIds, id) + } + return subscribeIds, nil +} + +func (ctl *messageSubscribeAdapter) UpdateSubsConfig(cmd CmdToUpdateSubscribe, + userName string) error { + if result := postgresql.DB().Table("message_center.subscribe_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where(gorm.Expr("is_default = ?", false)). + Where("source = ? AND mode_name = ?", cmd.Source, cmd.OldName). + Where("user_name = ?", userName). + Update("mode_name", cmd.NewName); result.Error != nil { + logrus.Errorf("update subscribe config failed, err:%v", result.Error) + return xerrors.Errorf("更新配置失败") + } + return nil +} + +func (ctl *messageSubscribeAdapter) RemoveSubsConfig(cmd CmdToDeleteSubscribe, userName string) error { + if result := postgresql.DB().Table("message_center.subscribe_config"). + Where(gorm.Expr("is_deleted = ?", false)). + Where(gorm.Expr("is_default = ?", false)). + Where("source = ? AND mode_name = ?", cmd.Source, cmd.ModeName). + Where("user_name = ?", userName). + Update("is_deleted", true); result.Error != nil { + return xerrors.Errorf("删除配置失败") + } + return nil +} diff --git a/message/infrastructure/trans.go b/message/infrastructure/trans.go new file mode 100644 index 0000000..d24956d --- /dev/null +++ b/message/infrastructure/trans.go @@ -0,0 +1,293 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package infrastructure + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "golang.org/x/xerrors" + "gorm.io/datatypes" + + "github.com/opensourceways/message-manager/utils" +) + +func TransToDbFormat(source string, eventType string, request CmdToGetSubscribe) (datatypes.JSON, + error) { + if utils.IsEurMessage(source) { + return TransEurModeFilterToDbFormat(request) + } else if utils.IsGiteeMessage(source) { + return TransGiteeModeFilterToDbFormat(eventType, request) + } else if utils.IsMeetingMessage(source) { + return TransMeetingModeFilterToDbFormat(request) + } else if utils.IsCveMessage(source) { + return TransCveModeFilterToDbFormat(request) + } + return nil, xerrors.Errorf("can not trans, source : %v", source) +} + +func buildStringFilter(values []string) string { + values = utils.RemoveEmptyStrings(values) + if len(values) == 1 { + return "eq=" + values[0] + } else if len(values) > 1 { + return "oneof=" + strings.Join(values, " ") + } + return "" +} + +func buildTimeFilter(startTime, endTime *time.Time) string { + if startTime != nil && endTime != nil { + return fmt.Sprintf("gt=%s,lt=%s", startTime.Format(time.DateTime), + endTime.Format(time.DateTime)) + } + return "" +} + +func marshalToJson(param interface{}) (datatypes.JSON, error) { + jsonStr, err := json.Marshal(param) + if err != nil { + return datatypes.JSON{}, err + } + return jsonStr, nil +} + +// TransEurModeFilterToDbFormat 处理 Eur 过滤器 +func TransEurModeFilterToDbFormat(modeFilter CmdToGetSubscribe) (datatypes.JSON, error) { + dbModeFilter := utils.EurDbFormat{ + Owner: buildStringFilter(strings.Split(modeFilter.BuildOwner, ",")), + User: buildStringFilter(strings.Split(modeFilter.BuildCreator, ",")), + Status: buildStringFilter(strings.Split(modeFilter.BuildStatus, ",")), + Env: buildStringFilter(strings.Split(modeFilter.BuildEnv, ",")), + EventTime: buildTimeFilter(utils.ParseUnixTimestamp(modeFilter.StartTime), + utils.ParseUnixTimestamp(modeFilter.EndTime)), + } + return marshalToJson(dbModeFilter) +} + +// 获取机器人过滤器 +func getBotFilter(isBot string) string { + if isBot == "true" { + return "oneof=openeuler-ci-bot ci-robot openeuler-sync-bot" + } else if isBot == "false" { + return "ne=openeuler-ci-bot,ne=ci-robot,ne=openeuler-sync-bot" + } + return "" +} + +func getStateFilter(prState string) (state, action, mergeStatus string) { + // 处理 PR 状态、动作和合并状态 + if prState != "" { + prStates := utils.RemoveEmptyStrings(strings.Split(prState, ",")) + var lStatus, lAction, lMergeStatus []string + + for _, ps := range prStates { + switch ps { + case "can_be_merged", "cannot_be_merged": + lMergeStatus = append(lMergeStatus, ps) + case "set_draft": + lAction = append(lAction, ps) + default: + lStatus = append(lStatus, ps) + } + } + + if len(lStatus) == 1 { + state = "eq=" + lStatus[0] + } else if len(lStatus) > 1 { + state = "oneof=" + strings.Join(lStatus, " ") + } + + if len(lAction) == 1 { + action = "eq=" + lAction[0] + } else if len(lAction) > 1 { + action = "oneof=" + strings.Join(lAction, " ") + } + + if len(lMergeStatus) == 1 { + mergeStatus = "eq=" + lMergeStatus[0] + } else if len(lMergeStatus) > 1 { + mergeStatus = "oneof=" + strings.Join(lMergeStatus, " ") + } + } + return +} + +func getOneOfFilter(value string) string { + if value != "" { + return "oneof=" + value + } + return "" +} + +func getNotOneOfFilter(value string) string { + if value != "" { + return "oneof!=" + value + } + return "" +} + +func getPrAssigneeFilter(value string) string { + if value != "" { + return "contains=" + value + } + return "" +} + +// 获取 Gitee 数据库格式 +func getGiteeDbFormat(eventType string, modeFilter CmdToGetSubscribe, lRepoName, + lNameSpace []string) interface{} { + sRepoName := buildStringFilter(lRepoName) + sNamespace := buildStringFilter(lNameSpace) + sMyManagement := getOneOfFilter(modeFilter.MyManagement) + sOtherManagement := getNotOneOfFilter(modeFilter.OtherManagement) + sMySig := getOneOfFilter(modeFilter.MySig) + sOtherSig := getNotOneOfFilter(modeFilter.OtherSig) + sSigGroupName := buildStringFilter(strings.Split(modeFilter.GiteeSigs, ",")) + sIsBot := getBotFilter(modeFilter.IsBot) + eventTime := buildTimeFilter(utils.ParseUnixTimestamp(modeFilter.StartTime), + utils.ParseUnixTimestamp(modeFilter.EndTime)) + + sState, sAction, sMergeStatus := getStateFilter(modeFilter.PrState) + + switch eventType { + case "issue": + return utils.GiteeIssueDbFormat{ + RepoName: sRepoName, + IsBot: sIsBot, + Namespace: sNamespace, + SigGroupName: sSigGroupName, + IssueState: buildStringFilter(strings.Split(modeFilter.IssueState, ",")), + IssueCreator: buildStringFilter(strings.Split(modeFilter.IssueCreator, ",")), + IssueAssignee: buildStringFilter(strings.Split(modeFilter.IssueAssignee, ",")), + EventTime: eventTime, + MySig: sMySig, + MyManagement: sMyManagement, + OtherSig: sOtherSig, + OtherManagement: sOtherManagement, + } + case "note": + return utils.GiteeNoteDbFormat{ + RepoName: sRepoName, + IsBot: sIsBot, + Namespace: sNamespace, + SigGroupName: sSigGroupName, + NoteType: buildStringFilter(strings.Split(modeFilter.NoteType, ",")), + About: buildStringFilter([]string{modeFilter.About}), + EventTime: eventTime, + MySig: sMySig, + MyManagement: sMyManagement, + OtherSig: sOtherSig, + OtherManagement: sOtherManagement, + } + case "pr": + return utils.GiteePullRequestDbFormat{ + RepoName: sRepoName, + IsBot: sIsBot, + Namespace: sNamespace, + SigGroupName: sSigGroupName, + PrState: sState, + PrAction: sAction, + PrMergeStatus: sMergeStatus, + PrCreator: buildStringFilter(strings.Split(modeFilter.PrCreator, ",")), + PrAssignee: getPrAssigneeFilter(modeFilter.PrAssignee), + EventTime: eventTime, + MySig: sMySig, + MyManagement: sMyManagement, + OtherSig: sOtherSig, + OtherManagement: sOtherManagement, + } + default: + return nil + } +} + +// TransGiteeModeFilterToDbFormat 处理 GiteeMode 过滤器 +func TransGiteeModeFilterToDbFormat(eventType string, modeFilter CmdToGetSubscribe) ( + datatypes.JSON, error) { + repoNames := utils.RemoveEmptyStrings(strings.Split(modeFilter.Repos, ",")) + var lRepoName, lNameSpace []string + + // 处理仓库名和命名空间 + for _, repoName := range utils.MergePaths(repoNames) { + if repoName == "*" { + lRepoName, lNameSpace = []string{}, []string{} + break + } + lName := strings.Split(repoName, "/") + if lName[1] == "*" { + lNameSpace = append(lNameSpace, lName[0]) + } else { + lRepoName = append(lRepoName, repoName) + } + } + dbModeFilter := getGiteeDbFormat(eventType, modeFilter, lRepoName, lNameSpace) + return marshalToJson(dbModeFilter) +} + +func buildMeetingDateFilter(meetingTime *time.Time) string { + if meetingTime != nil { + return fmt.Sprintf("eq=%s", meetingTime.Format(time.DateOnly)) + } + return "" +} + +// TransMeetingModeFilterToDbFormat 处理 MeetingMode 过滤器 +func TransMeetingModeFilterToDbFormat(modeFilter CmdToGetSubscribe) (datatypes.JSON, error) { + dbModeFilter := utils.MeetingDbFormat{ + Action: buildStringFilter(strings.Split(modeFilter.MeetingAction, ",")), + SigGroup: buildStringFilter(strings.Split(modeFilter.MeetingSigGroup, ",")), + Date: buildMeetingDateFilter(utils.ParseUnixTimestamp(modeFilter.MeetingStartTime)), + MySig: getOneOfFilter(modeFilter.MySig), + OtherSig: getNotOneOfFilter(modeFilter.OtherSig), + } + return marshalToJson(dbModeFilter) +} + +// 处理 CVE 组件过滤器 +func buildCveComponentFilter(component string) string { + if component == "" { + return "" + } + lComponent := strings.Split(component, ",") + var template []string + for _, comp := range lComponent { + template = append(template, "contains="+comp) + } + return "or " + strings.Join(template, " ") +} + +// 处理 CVE 影响过滤器 +func buildCveAffectedFilter(affected string) string { + if affected == "" { + return "" + } + lAffected := strings.Split(affected, ",") + var template []string + for _, aff := range lAffected { + template = append(template, "contains="+aff) + } + if len(template) == 1 { + return template[0] + } else if len(template) > 1 { + return "or " + strings.Join(template, " ") + } + return "" +} + +// TransCveModeFilterToDbFormat 处理 CVE 过滤器 +func TransCveModeFilterToDbFormat(modeFilter CmdToGetSubscribe) (datatypes.JSON, error) { + dbModeFilter := utils.CveDbFormat{ + Component: buildCveComponentFilter(modeFilter.CVEComponent), + State: buildStringFilter(strings.Split(modeFilter.IssueState, ",")), + Affected: buildCveAffectedFilter(modeFilter.CVEAffected), + SigGroupName: buildStringFilter(strings.Split(modeFilter.GiteeSigs, ",")), + MySig: getOneOfFilter(modeFilter.MySig), + OtherSig: getNotOneOfFilter(modeFilter.OtherSig), + } + return marshalToJson(dbModeFilter) +} diff --git a/message/infrastructure/trans_test.go b/message/infrastructure/trans_test.go new file mode 100644 index 0000000..4bd44cb --- /dev/null +++ b/message/infrastructure/trans_test.go @@ -0,0 +1,273 @@ +package infrastructure + +import ( + "encoding/json" + "testing" + + "github.com/opensourceways/message-manager/utils" +) + +func TestTransToDbFormat(t *testing.T) { + tests := []struct { + source string + eventType string + filter CmdToGetSubscribe + expectErr bool + }{ + { + source: utils.EurSource, + eventType: "issue", + filter: CmdToGetSubscribe{ + BuildOwner: "owner1", + BuildCreator: "creator1", + BuildStatus: "success", + BuildEnv: "prod", + StartTime: "1622505600", + EndTime: "1622592000", + }, + expectErr: false, + }, + { + source: utils.GiteeSource, + eventType: "note", + filter: CmdToGetSubscribe{ + Repos: "repo1/repo2", + MyManagement: "management1", + MySig: "sig1", + }, + expectErr: false, + }, + { + source: utils.MeetingSource, + eventType: "meeting", + filter: CmdToGetSubscribe{ + MeetingAction: "start,stop", + MeetingSigGroup: "group1", + StartTime: "1622505600", + EndTime: "1622592000", + }, + expectErr: false, + }, + { + source: utils.CveSource, + eventType: "cve", + filter: CmdToGetSubscribe{ + CVEComponent: "component1,component2", + IssueState: "open,closed", + }, + expectErr: false, + }, + { + source: "unknown", + eventType: "issue", + filter: CmdToGetSubscribe{}, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.source, func(t *testing.T) { + result, err := TransToDbFormat(tt.source, tt.eventType, tt.filter) + if (err != nil) != tt.expectErr { + t.Errorf("TransToDbFormat() error = %v, wantErr %v", err, tt.expectErr) + } + + if !tt.expectErr { + var dbFormat map[string]interface{} + if err := json.Unmarshal(result, &dbFormat); err != nil { + t.Fatalf("Failed to unmarshal result: %v", err) + } + } + }) + } +} + +func TestTransEurModeFilterToDbFormat(t *testing.T) { + tests := []struct { + filter CmdToGetSubscribe + expectErr bool + }{ + { + filter: CmdToGetSubscribe{ + BuildOwner: "owner1", + BuildCreator: "creator1", + BuildStatus: "success", + BuildEnv: "prod", + StartTime: "1622505600", + EndTime: "1622592000", + }, + expectErr: false, + }, + { + filter: CmdToGetSubscribe{ + BuildOwner: "owner2,owner3", + BuildCreator: "", + BuildStatus: "", + BuildEnv: "test", + StartTime: "1622505600", + EndTime: "1622592000", + }, + expectErr: false, + }, + { + filter: CmdToGetSubscribe{ + BuildOwner: "", + BuildCreator: "creator3", + BuildStatus: "failed", + BuildEnv: "", + StartTime: "", // 测试空时间 + EndTime: "", + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.filter.BuildOwner, func(t *testing.T) { + result, err := TransEurModeFilterToDbFormat(tt.filter) + if (err != nil) != tt.expectErr { + t.Errorf("TransEurModeFilterToDbFormat() error = %v, wantErr %v", err, tt.expectErr) + } + + if !tt.expectErr { + var dbFormat map[string]interface{} + if err := json.Unmarshal(result, &dbFormat); err != nil { + t.Fatalf("Failed to unmarshal result: %v", err) + } + } + }) + } +} + +func TestTransGiteeModeFilterToDbFormat(t *testing.T) { + tests := []struct { + eventType string + filter CmdToGetSubscribe + expectErr bool + }{ + { + eventType: "issue", + filter: CmdToGetSubscribe{ + Repos: "repo1/repo2", + MyManagement: "management1", + MySig: "sig1", + }, + expectErr: false, + }, + { + eventType: "note", + filter: CmdToGetSubscribe{ + Repos: "*", // 特殊情况 + MyManagement: "management2", + MySig: "sig2", + }, + expectErr: false, + }, + { + eventType: "pr", + filter: CmdToGetSubscribe{ + IssueState: "open", + PrCreator: "creator1", + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.eventType, func(t *testing.T) { + result, err := TransGiteeModeFilterToDbFormat(tt.eventType, tt.filter) + if (err != nil) != tt.expectErr { + t.Errorf("TransGiteeModeFilterToDbFormat() error = %v, wantErr %v", err, tt.expectErr) + } + + if !tt.expectErr { + var dbFormat map[string]interface{} + if err := json.Unmarshal(result, &dbFormat); err != nil { + t.Fatalf("Failed to unmarshal result: %v", err) + } + } + }) + } +} + +func TestTransMeetingModeFilterToDbFormat(t *testing.T) { + tests := []struct { + filter CmdToGetSubscribe + expectErr bool + }{ + { + filter: CmdToGetSubscribe{ + MeetingAction: "start", + MeetingSigGroup: "group1", + StartTime: "1622505600", + EndTime: "1622592000", + }, + expectErr: false, + }, + { + filter: CmdToGetSubscribe{ + MeetingAction: "stop", + MeetingSigGroup: "group2", + StartTime: "1622505600", + EndTime: "1622592000", + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.filter.MeetingAction, func(t *testing.T) { + result, err := TransMeetingModeFilterToDbFormat(tt.filter) + if (err != nil) != tt.expectErr { + t.Errorf("TransMeetingModeFilterToDbFormat() error = %v, wantErr %v", err, tt.expectErr) + } + + if !tt.expectErr { + var dbFormat map[string]interface{} + if err := json.Unmarshal(result, &dbFormat); err != nil { + t.Fatalf("Failed to unmarshal result: %v", err) + } + } + }) + } +} + +func TestTransCveModeFilterToDbFormat(t *testing.T) { + tests := []struct { + filter CmdToGetSubscribe + expectErr bool + }{ + { + filter: CmdToGetSubscribe{ + CVEComponent: "component1", + IssueState: "open", + CVEAffected: "affected1", + }, + expectErr: false, + }, + { + filter: CmdToGetSubscribe{ + CVEComponent: "component2", + IssueState: "closed", + CVEAffected: "affected2", + }, + expectErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.filter.CVEComponent, func(t *testing.T) { + result, err := TransCveModeFilterToDbFormat(tt.filter) + if (err != nil) != tt.expectErr { + t.Errorf("TransCveModeFilterToDbFormat() error = %v, wantErr %v", err, tt.expectErr) + } + + if !tt.expectErr { + var dbFormat map[string]interface{} + if err := json.Unmarshal(result, &dbFormat); err != nil { + t.Fatalf("Failed to unmarshal result: %v", err) + } + } + }) + } +} diff --git a/server/gin.go b/server/gin.go new file mode 100644 index 0000000..43c4081 --- /dev/null +++ b/server/gin.go @@ -0,0 +1,92 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package server + +import ( + "fmt" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/opensourceways/server-common-lib/interrupts" + "github.com/sirupsen/logrus" + swaggerfiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + + "github.com/opensourceways/message-manager/docs" +) + +const ( + version = "development" // program version for this build + apiDesc = "message server manager APIs" + apiTitle = "message server" +) + +func StartWebServer() { + engine := gin.New() + engine.Use(gin.Recovery()) + engine.Use(logRequest()) + engine.UseRawPath = true + + docs.SwaggerInfo.Title = apiTitle + docs.SwaggerInfo.Version = version + docs.SwaggerInfo.Description = apiDesc + + services, err := initServices() + if err != nil { + return + } + + setRouterOfInternal(engine, &services) + + // start server + srv := &http.Server{ + Addr: fmt.Sprintf(":%d", 8082), + Handler: engine, + } + + defer interrupts.WaitForGracefulShutdown() + interrupts.ListenAndServe(srv, time.Second*30) + + engine.UseRawPath = true + engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) +} + +func logRequest() gin.HandlerFunc { + return func(c *gin.Context) { + startTime := time.Now() + + c.Next() + + endTime := time.Now() + + errmsg := "" + for _, ginErr := range c.Errors { + if errmsg != "" { + errmsg += "," + } + errmsg = fmt.Sprintf("%s%s", errmsg, ginErr.Error()) + } + + if strings.Contains(c.Request.RequestURI, "/swagger/") || + strings.Contains(c.Request.RequestURI, "/internal/heartbeat") { + return + } + + log := fmt.Sprintf( + "| %d | %d | %s | %s ", + c.Writer.Status(), + endTime.Sub(startTime), + c.Request.Method, + c.Request.RequestURI, + ) + if errmsg != "" { + log += fmt.Sprintf("| %s ", errmsg) + } + + logrus.Info(log) + } +} diff --git a/server/gin_test.go b/server/gin_test.go new file mode 100644 index 0000000..1028049 --- /dev/null +++ b/server/gin_test.go @@ -0,0 +1,74 @@ +// server/server_test.go +package server + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" +) + +func TestStartWebServer(t *testing.T) { + + // 使用 httptest 创建一个新请求 + req, err := http.NewRequest("GET", "/", nil) + assert.NoError(t, err) + w := httptest.NewRecorder() + + // 启动 Web 服务器 + go StartWebServer() + + // 发送请求到服务器 + w.WriteHeader(http.StatusOK) + router := gin.New() + router.GET("/", func(c *gin.Context) { + c.String(http.StatusOK, "Hello, World!") + }) + router.ServeHTTP(w, req) + + // 验证响应 + assert.Equal(t, http.StatusOK, w.Code) + assert.Equal(t, "Hello, World!", w.Body.String()) +} + +func TestLogRequest(t *testing.T) { + // 创建一个新的 gin 引擎 + gin.SetMode(gin.TestMode) + r := gin.New() + r.Use(logRequest()) + + // 测试请求 + w := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, "/test", nil) + assert.NoError(t, err) + // 记录请求 + r.ServeHTTP(w, req) + + // 验证响应状态码 + assert.Equal(t, http.StatusNotFound, w.Code) +} + +func TestLogRequestWithError(t *testing.T) { + // 创建一个新的 gin 引擎 + gin.SetMode(gin.TestMode) + r := gin.New() + r.Use(logRequest()) + r.GET("/error", func(c *gin.Context) { + c.Error(fmt.Errorf("test error")) + c.String(http.StatusInternalServerError, "Internal Server Error") + }) + + // 测试请求 + w := httptest.NewRecorder() + req, err := http.NewRequest(http.MethodGet, "/error", nil) + assert.NoError(t, err) + // 记录请求 + r.ServeHTTP(w, req) + + // 验证响应状态码 + assert.Equal(t, http.StatusInternalServerError, w.Code) + assert.Contains(t, w.Body.String(), "Internal Server Error") +} diff --git a/server/internal.go b/server/internal.go new file mode 100644 index 0000000..306a5a8 --- /dev/null +++ b/server/internal.go @@ -0,0 +1,13 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package server + +import ( + "github.com/gin-gonic/gin" +) + +func setRouterOfInternal(engine *gin.Engine, services *allServices) { + setRouteOfMessage(engine, services) +} diff --git a/server/message.go b/server/message.go new file mode 100644 index 0000000..dd4b415 --- /dev/null +++ b/server/message.go @@ -0,0 +1,50 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package server + +import ( + "github.com/gin-gonic/gin" + + "github.com/opensourceways/message-manager/message/app" + messagectl "github.com/opensourceways/message-manager/message/controller" + "github.com/opensourceways/message-manager/message/infrastructure" +) + +func initMessage(services *allServices) error { + services.MessageListAppService = app.NewMessageListAppService( + infrastructure.MessageListAdapter(), + ) + services.MessagePushAppService = app.NewMessagePushAppService( + infrastructure.MessagePushAdapter(), + ) + services.MessageRecipientAppService = app.NewMessageRecipientAppService( + infrastructure.MessageRecipientAdapter(), + ) + services.MessageSubscribeAppService = app.NewMessageSubscribeAppService( + infrastructure.MessageSubscribeAdapter(), + ) + + return nil +} + +// setRouteOfMessage is registering controller of moderation in api +func setRouteOfMessage(rg *gin.Engine, services *allServices) { + messagectl.AddRouterForMessageListController( + rg, + services.MessageListAppService, + ) + messagectl.AddRouterForMessagePushController( + rg, + services.MessagePushAppService, + ) + messagectl.AddRouterForMessageRecipientController( + rg, + services.MessageRecipientAppService, + ) + messagectl.AddRouterForMessageSubscribeController( + rg, + services.MessageSubscribeAppService, + ) +} diff --git a/server/service.go b/server/service.go new file mode 100644 index 0000000..88f7727 --- /dev/null +++ b/server/service.go @@ -0,0 +1,24 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package server + +import ( + "github.com/opensourceways/message-manager/message/app" +) + +type allServices struct { + MessageListAppService app.MessageListAppService + MessagePushAppService app.MessagePushAppService + MessageRecipientAppService app.MessageRecipientAppService + MessageSubscribeAppService app.MessageSubscribeAppService +} + +// initServices init All service +func initServices() (services allServices, err error) { + if err = initMessage(&services); err != nil { + return + } + return +} diff --git a/utils/models.go b/utils/models.go new file mode 100644 index 0000000..6188674 --- /dev/null +++ b/utils/models.go @@ -0,0 +1,97 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package utils + +type SigData struct { + Sig []string `json:"sig"` + Type []string `json:"type"` +} + +type SigInfo struct { + SigData `json:"data"` +} + +type GiteeRepo struct { + FullName string `json:"full_name"` + Permission `json:"permission"` +} + +type Permission struct { + Pull bool `json:"pull"` + Push bool `json:"push"` + Admin bool `json:"admin"` +} + +type EurDbFormat struct { + Status string `json:"Body.Status,omitempty"` + Owner string `json:"Body.Owner,omitempty"` + User string `json:"Body.User,omitempty"` + Env string `json:"Body.Chroot,omitempty"` + EventTime string `json:"EventTime,omitempty"` +} + +type GiteeIssueDbFormat struct { + RepoName string `json:"IssueEvent.Repository.FullName,omitempty"` + IsBot string `json:"IssueEvent.Sender.Name,omitempty"` + Namespace string `json:"IssueEvent.Repository.Namespace,omitempty"` + SigGroupName string `json:"SigGroupName,omitempty"` + IssueState string `json:"IssueEvent.Issue.State,omitempty"` + IssueCreator string `json:"IssueEvent.Issue.User.Login,omitempty"` + IssueAssignee string `json:"IssueEvent.Assignee.Login,omitempty"` + EventTime string `json:"EventTime,omitempty"` + MyManagement string `json:"RepoAdmins,omitempty"` + OtherManagement string `json:"RepoAdmins,omitempty"` + MySig string `json:"SigMaintainers,omitempty"` + OtherSig string `json:"SigMaintainers,omitempty"` +} +type GiteeNoteDbFormat struct { + RepoName string `json:"NoteEvent.Repository.FullName,omitempty"` + IsBot string `json:"NoteEvent.Sender.Name,omitempty"` + Namespace string `json:"NoteEvent.Repository.Namespace,omitempty"` + SigGroupName string `json:"SigGroupName,omitempty"` + NoteType string `json:"NoteEvent.NoteableType,omitempty"` + About string `json:"NoteEvent.Comment.Body,omitempty"` + EventTime string `json:"EventTime,omitempty"` + MyManagement string `json:"RepoAdmins,omitempty"` + OtherManagement string `json:"RepoAdmins,omitempty"` + MySig string `json:"SigMaintainers,omitempty"` + OtherSig string `json:"SigMaintainers,omitempty"` +} +type GiteePullRequestDbFormat struct { + RepoName string `json:"PullRequestEvent.Repository.FullName,omitempty"` + IsBot string `json:"PullRequestEvent.Sender.Name,omitempty"` + Namespace string `json:"PullRequestEvent.Repository.Namespace,omitempty"` + SigGroupName string `json:"SigGroupName,omitempty"` + PrState string `json:"PullRequestEvent.PullRequest.State,omitempty"` + PrAction string `json:"PullRequestEvent.Action,omitempty"` + PrMergeStatus string `json:"PullRequestEvent.MergeStatus,omitempty"` + PrCreator string `json:"PullRequestEvent.PullRequest.User.Login,omitempty"` + PrAssignee string `json:"PullRequestEvent.PullRequest.Assignee.Login,omitempty"` + EventTime string `json:"EventTime,omitempty"` + MyManagement string `json:"RepoAdmins,omitempty"` + OtherManagement string `json:"RepoAdmins,omitempty"` + MySig string `json:"SigMaintainers,omitempty"` + OtherSig string `json:"SigMaintainers,omitempty"` +} + +type GiteeNoTypeDbFormat struct { +} + +type MeetingDbFormat struct { + Action string `json:"Action,omitempty"` + SigGroup string `json:"Msg.GroupName,omitempty"` + Date string `json:"Msg.Date,omitempty"` + MySig string `json:"SigMaintainers,omitempty"` + OtherSig string `json:"SigMaintainers,omitempty"` +} + +type CveDbFormat struct { + Component string `json:"CVEComponent,omitempty"` + State string `json:"IssueEvent.Issue.State,omitempty"` + SigGroupName string `json:"SigGroupName,omitempty"` + Affected string `json:"CVEAffectVersion,omitempty"` + MySig string `json:"SigMaintainers,omitempty"` + OtherSig string `json:"SigMaintainers,omitempty"` +} diff --git a/utils/util.go b/utils/util.go new file mode 100644 index 0000000..383ff6b --- /dev/null +++ b/utils/util.go @@ -0,0 +1,211 @@ +/* +Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved +*/ + +package utils + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "sort" + "strconv" + "strings" + "time" + + "golang.org/x/xerrors" +) + +const ( + EurSource = "https://eur.openeuler.openatom.cn" + GiteeSource = "https://gitee.com" + MeetingSource = "https://www.openEuler.org/meeting" + CveSource = "cve" + + eulerUserSigUrl = "https://dsapi.osinfra.cn/query/user/ownertype?community=openeuler&user=%s" + giteeUserReposUrl = "https://gitee.com/api/v5/users/%s/repos?type=all&sort=full_name&page=%d&per_page=%d" +) + +func ParseUnixTimestamp(timestampStr string) *time.Time { + if timestampStr == "" { + return nil + } + // 解析字符串为整数 + timestamp, err := strconv.ParseInt(timestampStr, 10, 64) + if err != nil { + return nil + } + // 将毫秒转换为秒和纳秒 + seconds := timestamp / 1000 + nanoseconds := (timestamp % 1000) * 1000000 + t := time.Unix(seconds, nanoseconds) + utcPlus8 := t.Add(8 * time.Hour) + return &utcPlus8 +} + +func IsEurMessage(source string) bool { + return source == EurSource +} + +func IsGiteeMessage(source string) bool { + return source == GiteeSource +} + +func IsMeetingMessage(source string) bool { + return source == MeetingSource +} + +func IsCveMessage(source string) bool { + return source == CveSource +} + +func sortStringList(strList []string) []string { + // 定义一个比较函数,用于排序 + less := func(i, j int) bool { + // 如果一个字符串包含 "*",另一个不包含,则把包含 "*" 的排在前面 + if strings.Contains(strList[i], "*") && !strings.Contains(strList[j], "*") { + return true + } + if !strings.Contains(strList[i], "*") && strings.Contains(strList[j], "*") { + return false + } + // 如果两个字符串都包含或都不包含 "*",则按原来的顺序排列 + return strList[i] < strList[j] + } + + // 使用自定义的比较函数进行排序 + sort.Slice(strList, less) + return strList +} + +func MergePaths(paths []string) []string { + pathDict := make(map[string]bool) + var result []string + sortPaths := sortStringList(paths) + + // 遍历路径列表 + for _, p := range sortPaths { + if p == "" { + continue + } + + if p == "*" { + result = []string{"*"} + break + } + + lp := strings.Split(p, "/") + if strings.Contains(p, "*") { + result = append(result, p) + pathDict[lp[0]] = true + } else { + if pathDict[lp[0]] { + continue + } else { + result = append(result, p) + } + } + } + return result +} + +func RemoveEmptyStrings(input []string) []string { + var result []string + for _, str := range input { + if str != "" { + result = append(result, str) + } + } + return result +} + +func GetUserSigInfo(userName string) ([]string, error) { + url := fmt.Sprintf(eulerUserSigUrl, userName) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return []string{}, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return []string{}, err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return []string{}, err + } + + var repoSig SigInfo + err = json.Unmarshal(body, &repoSig) + if err != nil { + return []string{}, err + } + if repoSig.Sig == nil { + return []string{}, nil // 确保返回空切片而不是 nil + } + return repoSig.Sig, nil +} + +func GetUserAdminRepos(userName string) ([]string, error) { + var repos []GiteeRepo + page := 1 + perPage := 100 + + var totalCount int + + for { + url := fmt.Sprintf(giteeUserReposUrl, userName, page, perPage) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return []string{}, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return []string{}, err + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return []string{}, err + } + + var members []GiteeRepo + err = json.Unmarshal(body, &members) + if err != nil { + return []string{}, err + } + + repos = append(repos, members...) + + if totalCount == 0 { + totalCount, err = strconv.Atoi(resp.Header.Get("total_count")) + if err != nil { + return []string{}, xerrors.Errorf("trans to int failed, err:%v", err) + } + } + + if len(members) < perPage { + break + } + page++ + err = resp.Body.Close() + if err != nil { + return []string{}, xerrors.Errorf("close body failed, err :%v", err) + } + } + + var adminRepos []string + for _, repo := range repos { + if repo.Admin { + adminRepos = append(adminRepos, repo.FullName) + } + } + if len(adminRepos) == 0 { + return []string{}, nil // 确保返回空切片而不是 nil + } + return adminRepos, nil +} diff --git a/utils/util_test.go b/utils/util_test.go new file mode 100644 index 0000000..2f51ed2 --- /dev/null +++ b/utils/util_test.go @@ -0,0 +1,182 @@ +// utils/utils_test.go +package utils + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestParseUnixTimestamp(t *testing.T) { + tests := []struct { + input string + expected *time.Time + }{ + {"", nil}, // 测试空字符串 + {"1633072800", func() *time.Time { + t := time.Unix(1633072800, 0) + return &t + }()}, + {"invalid", nil}, // 测试无效的时间戳 + } + + for _, test := range tests { + result := ParseUnixTimestamp(test.input) + if (result == nil && test.expected != nil) || (result != nil && test.expected == nil) { + t.Errorf("expected %v, got %v", test.expected, result) + } else if result != nil && !result.Equal(*test.expected) { + t.Errorf("expected %v, got %v", *test.expected, *result) + } + } +} + +func TestIsEurMessage(t *testing.T) { + if !IsEurMessage(EurSource) { + t.Errorf("IsEurMessage should return true for EurSource") + } + if IsEurMessage("invalid") { + t.Errorf("IsEurMessage should return false for invalid source") + } +} + +func TestIsGiteeMessage(t *testing.T) { + if !IsGiteeMessage(GiteeSource) { + t.Errorf("IsGiteeMessage should return true for GiteeSource") + } + if IsGiteeMessage("invalid") { + t.Errorf("IsGiteeMessage should return false for invalid source") + } +} + +func TestIsMeetingMessage(t *testing.T) { + if !IsMeetingMessage(MeetingSource) { + t.Errorf("IsMeetingMessage should return true for MeetingSource") + } + if IsMeetingMessage("invalid") { + t.Errorf("IsMeetingMessage should return false for invalid source") + } +} + +func TestIsCveMessage(t *testing.T) { + if !IsCveMessage(CveSource) { + t.Errorf("IsCveMessage should return true for CveSource") + } + if IsCveMessage("invalid") { + t.Errorf("IsCveMessage should return false for invalid source") + } +} + +func TestSortStringList(t *testing.T) { + input := []string{"b", "a", "*", "c", "d*"} + expected := []string{"*", "d*", "a", "b", "c"} + result := sortStringList(input) + + for i, v := range expected { + if result[i] != v { + t.Errorf("expected %v, got %v", expected, result) + break + } + } +} + +func TestMergePaths(t *testing.T) { + input := []string{"path1/*", "path2/*", "*", "path3/*", "path1/subpath"} + expected := []string{"*"} + result := MergePaths(input) + + if len(result) != len(expected) || result[0] != expected[0] { + t.Errorf("expected %v, got %v", expected, result) + } + + input2 := []string{"path1/*", "path2/*", "path1/subpath"} + expected2 := []string{"path1/*", "path2/*"} + result2 := MergePaths(input2) + + for i, v := range expected2 { + if result2[i] != v { + t.Errorf("expected %v, got %v", expected2, result2) + break + } + } +} + +func TestRemoveEmptyStrings(t *testing.T) { + input := []string{"", "test", "", "example"} + expected := []string{"test", "example"} + result := RemoveEmptyStrings(input) + + if len(result) != len(expected) { + t.Errorf("expected length %d, got %d", len(expected), len(result)) + } + + for i, v := range expected { + if result[i] != v { + t.Errorf("expected %v, got %v", expected, result) + break + } + } +} + +type TestSigInfo struct { + Sig []string `json:"sig"` +} + +type TestGiteeRepo struct { + FullName string `json:"full_name"` + Admin bool `json:"admin"` +} + +// 测试 GetUserSigInfo +func TestGetUserSigInfo(t *testing.T) { + // 创建一个 HTTP 测试服务器 + handler := func(w http.ResponseWriter, r *http.Request) { + // 模拟返回的 JSON 数据 + response := TestSigInfo{Sig: []string{}} + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(response) + if err != nil { + t.Fatalf("could not decode response: %v", err) + } + } + + server := httptest.NewServer(http.HandlerFunc(handler)) + defer server.Close() + + // 测试函数 + sigs, err := GetUserSigInfo("testuser") + assert.NoError(t, err) + assert.Equal(t, []string{}, sigs) +} + +// 测试 GetUserAdminRepos +func TestGetUserAdminRepos(t *testing.T) { + // 创建一个 HTTP 测试服务器 + handler := func(w http.ResponseWriter, r *http.Request) { + // 模拟返回的 JSON 数据 + repos := []TestGiteeRepo{ + {FullName: "testuser/Git-Demo", Admin: true}, + {FullName: "testuser/jhj", Admin: false}, + {FullName: "testuser/testtest", Admin: true}, + {FullName: "testuser/testtest2", Admin: true}, + } + // 设置 total_count 响应头 + w.Header().Set("total_count", "3") + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(repos) + if err != nil { + t.Fatalf("could not decode response: %v", err) + } + } + + server := httptest.NewServer(http.HandlerFunc(handler)) + defer server.Close() + + // 测试函数 + adminRepos, err := GetUserAdminRepos("testuser") + assert.NoError(t, err) + assert.Equal(t, []string{"testuser/Git-Demo", "testuser/jhj", "testuser/testtest", "testuser/testtest2"}, adminRepos) +}