diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 40585e3..8016d04 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,5 +1,5 @@ -name-template: 'release-v$NEXT_PATCH_VERSION' -tag-template: 'release-v$NEXT_PATCH_VERSION' +name-template: '$NEXT_PATCH_VERSION' +tag-template: '$NEXT_PATCH_VERSION' categories: - title: 'Features' labels: diff --git a/.github/workflows/upload-to-release.yml b/.github/workflows/upload-to-release.yml index 80b6271..2564c51 100644 --- a/.github/workflows/upload-to-release.yml +++ b/.github/workflows/upload-to-release.yml @@ -42,7 +42,7 @@ jobs: - name: Release Version id: release_version run: | - echo ::set-output name=tag::$(echo "${GITHUB_REF}" | sed -e "s/refs\/tags\///g" | sed -E "s/release-v?([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z]+(\.[0-9]+)?)?/\1.\2.\3\4/g") + echo ::set-output name=tag::$(echo "${GITHUB_REF}" | sed -e "s/refs\/tags\///g") - name: Gzip run: | @@ -72,7 +72,7 @@ jobs: - name: Release Version id: release_version run: | - echo ::set-output name=tag::$(echo "${GITHUB_REF}" | sed -e "s/refs\/tags\///g" | sed -E "s/release-v?([0-9]+)\.([0-9]+)\.([0-9]+)(-[a-zA-Z]+(\.[0-9]+)?)?/\1.\2.\3\4/g") + echo ::set-output name=tag::$(echo "${GITHUB_REF}" | sed -e "s/refs\/tags\///g") - name: Check out code into the Go module directory uses: actions/checkout@v1 diff --git a/config.example.json b/config.example.json index 0fdf6e6..3c75af0 100644 --- a/config.example.json +++ b/config.example.json @@ -1,9 +1,9 @@ { - "version": "3.3.0", + "version": "3.4.0", "address": "0.0.0.0", - "admin_url": "", "port": 8000, "secret": "!!! CHANGE THIS !!!", + "log_file": "pasteme.log", "database": { "type": "mysql", "username": "username", diff --git a/config/config.go b/config/config.go index fb7b127..034ce3b 100644 --- a/config/config.go +++ b/config/config.go @@ -21,9 +21,9 @@ type Database struct { type config struct { Version string `json:"version"` Address string `json:"address"` - AdminUrl string `json:"admin_url"` // PasteMe Admin's hostname Port uint16 `json:"port"` Secret string `json:"secret"` + LogFile string `json:"log_file"` Database Database `json:"database"` } @@ -32,10 +32,6 @@ var Config config func init() { load(flag.Config) checkVersion(Config.Version) - setDefault() -} - -func setDefault() { } func isInArray(item string, array []string) bool { @@ -66,7 +62,7 @@ func exportConfig(filename string, c config) { zap.String("config_file", filename), zap.String("config_version", c.Version), zap.String("address", c.Address), - zap.String("admin_url", c.AdminUrl), + zap.String("log_file", c.LogFile), zap.Uint16("port", c.Port), ) diff --git a/docker-compose.yml b/docker-compose.yml index 82b5428..86092ac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: depends_on: - pasteme-mysql healthcheck: - test: ["CMD", "wget", "localhost:8000/api/?method=beat", "--output=/dev/null"] + test: ["CMD", "wget", "localhost:8000/api/v3/?method=beat", "--output=/dev/null"] interval: 30s timeout: 3s retries: 3 diff --git a/go.mod b/go.mod index eba0a72..c25a39c 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,6 @@ require ( github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-sqlite3 v1.14.7 github.com/ugorji/go v1.2.6 // indirect - github.com/wonderivan/logger v1.0.0 go.uber.org/zap v1.18.1 golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect diff --git a/go.sum b/go.sum index e02205a..3e7f9fa 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,6 @@ github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= -github.com/wonderivan/logger v1.0.0 h1:Z6Nz+3SNcizolx3ARH11axdD4DXjFpb2J+ziGUVlv/U= -github.com/wonderivan/logger v1.0.0/go.mod h1:NObMfQ3WOLKfYEZuGeZQfuQfSPE5+QNgRddVMzsAT/k= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= diff --git a/handler/common/common.go b/handler/common/common.go new file mode 100644 index 0000000..8158204 --- /dev/null +++ b/handler/common/common.go @@ -0,0 +1,24 @@ +package common + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +func NotFoundHandler(context *gin.Context) { + context.JSON(http.StatusOK, gin.H{ + "status": http.StatusNotFound, + "message": ErrNoRouterFounded.Error(), + }) +} + +func Beat(context *gin.Context) { + method := context.DefaultQuery("method", "none") + if method == "beat" { + context.JSON(http.StatusOK, gin.H{ + "status": http.StatusOK, + }) + } else { + NotFoundHandler(context) + } +} diff --git a/handler/common/errors.go b/handler/common/errors.go new file mode 100644 index 0000000..717a958 --- /dev/null +++ b/handler/common/errors.go @@ -0,0 +1,7 @@ +package common + +import "errors" + +var ( + ErrNoRouterFounded = errors.New("no router founded") +) diff --git a/logging/logger_func.go b/logging/logger_func.go new file mode 100644 index 0000000..67d2ff1 --- /dev/null +++ b/logging/logger_func.go @@ -0,0 +1,40 @@ +package logging + +import ( + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func exportField(requests *gin.Context) []zap.Field { + var result []zap.Field + result = append(result, zap.String("ip", requests.ClientIP())) + return result +} + +func loggerPreprocess(fields []interface{}) []zap.Field { + var result []zap.Field + + if len(fields) != 0 { + var beginIndex = 0 + switch fields[0].(type) { + case *gin.Context: + context := fields[0].(*gin.Context) + zapField := exportField(context) + result = append(result, zapField...) + beginIndex = 1 + } + for _, each := range fields[beginIndex:] { + result = append(result, each.(zap.Field)) + } + } + + return result +} + +type loggerFunc func(string, ...interface{}) + +func getLogger(log func(string, ...zap.Field)) loggerFunc { + return func(msg string, a ...interface{}) { + log(msg, loggerPreprocess(a)...) + } +} diff --git a/logging/logging.go b/logging/logging.go index 7491286..8c09a22 100644 --- a/logging/logging.go +++ b/logging/logging.go @@ -1,45 +1,9 @@ package logging import ( - "github.com/gin-gonic/gin" "go.uber.org/zap" ) -func exportField(requests *gin.Context) []zap.Field { - var result []zap.Field - result = append(result, zap.String("ip", requests.ClientIP())) - return result -} - -func loggerPreprocess(fields []interface{}) []zap.Field { - var result []zap.Field - - if len(fields) != 0 { - var beginIndex = 0 - switch fields[0].(type) { - case *gin.Context: - context := fields[0].(*gin.Context) - zapField := exportField(context) - result = append(result, zapField...) - beginIndex = 1 - } - for _, each := range fields[beginIndex:] { - result = append(result, each.(zap.Field)) - } - } - - return result -} - -type loggerFunc func(string, ...interface{}) - -func getLogger(log func(string, ...zap.Field)) loggerFunc { - return func(msg string, a ...interface{}) { - log(msg, loggerPreprocess(a)...) - } -} - - var ( logger *zap.Logger Debug loggerFunc diff --git a/model/paste/generator.go b/model/paste/generator.go index d6ec865..0afa212 100644 --- a/model/paste/generator.go +++ b/model/paste/generator.go @@ -30,7 +30,7 @@ func generator(length int, zeroFirst bool) string { func Generator(length int, zeroFirst bool, model interface{}) string { str := generator(length, zeroFirst) - for exist(str, model) { + for Exist(str, model) { str = generator(length, zeroFirst) } return str diff --git a/model/paste/paste.go b/model/paste/paste.go index 76ed8cb..a6fce27 100644 --- a/model/paste/paste.go +++ b/model/paste/paste.go @@ -1,6 +1,7 @@ package paste import ( + "github.com/PasteUs/PasteMeGoBackend/model/dao" "github.com/PasteUs/PasteMeGoBackend/util" "time" ) @@ -44,3 +45,9 @@ func (paste *AbstractPaste) checkPassword(password string) error { } return ErrWrongPassword } + +func Exist(key string, model interface{}) bool { + count := uint8(0) + dao.DB.Model(model).Where("`key` = ?", key).Count(&count) + return count > 0 +} \ No newline at end of file diff --git a/model/paste/temporary.go b/model/paste/temporary.go index a57248a..010bece 100644 --- a/model/paste/temporary.go +++ b/model/paste/temporary.go @@ -69,8 +69,4 @@ func (paste *Temporary) Get(password string) error { return err } -func exist(key string, model interface{}) bool { - count := uint8(0) - dao.DB.Model(model).Where("`key` = ?", key).Count(&count) - return count > 0 -} + diff --git a/router/router.go b/router/router.go index dea220b..6f78365 100644 --- a/router/router.go +++ b/router/router.go @@ -3,10 +3,10 @@ package router import ( "fmt" "github.com/PasteUs/PasteMeGoBackend/flag" + "github.com/PasteUs/PasteMeGoBackend/handler/common" "github.com/PasteUs/PasteMeGoBackend/handler/paste" "github.com/PasteUs/PasteMeGoBackend/handler/session" "github.com/PasteUs/PasteMeGoBackend/logging" - v2Handler "github.com/PasteUs/PasteMeGoBackend/v2/handler" "github.com/gin-gonic/gin" "go.uber.org/zap" ) @@ -21,20 +21,10 @@ func init() { api := router.Group("/api") { - api.GET("/", v2Handler.Beat) // 心跳检测 - - v2 := api.Group("/v2") - { - // 访问未加密的 Paste,token 为 - // 访问加密的 Paste,token 为 , - v2.GET("/:token", v2Handler.Query) - v2.POST("/", v2Handler.PermanentCreator) // 创建一个永久的 Paste, key 是自增键 - v2.POST("/once", v2Handler.ReadOnceCreator) // 创建一个阅后即焚的 Paste, key 是随机的 - v2.PUT("/:key", v2Handler.TemporaryCreator) // 创建一个阅后即焚的 Paste, key 是指定的 - } - v3 := api.Group("/v3") { + v3.GET("/", common.Beat) + s := v3.Group("/session") { s.POST("", session.AuthMiddleware.LoginHandler) // 创建 Session(登陆) @@ -58,7 +48,7 @@ func init() { } } - router.NoRoute(v2Handler.NotFoundHandler) + router.NoRoute(common.NotFoundHandler) } func Run(address string, port uint16) { diff --git a/router/router_test.go b/router/router_test.go index 78950ab..25bddfa 100644 --- a/router/router_test.go +++ b/router/router_test.go @@ -1,201 +1,153 @@ package router import ( + "bytes" "encoding/json" - "github.com/PasteUs/PasteMeGoBackend/tests/request" - "github.com/PasteUs/PasteMeGoBackend/util" - "github.com/PasteUs/PasteMeGoBackend/v2/model" + "fmt" + "io" + "io/ioutil" + "net/http/httptest" + "strings" "testing" ) -var keyP uint64 -var keyT, keyR string +func request(t *testing.T, method string, uri string, param map[string]interface{}, + header map[string]string) (result map[string]interface{}) { + var body io.Reader = nil -func checkGetResponse(t *testing.T, body []byte) { - type JsonResponse struct { - Content string `json:"content"` - Lang string `json:"lang"` + if method == "GET" { + var rawQueryList []string + for k, v := range param { + rawQueryList = append(rawQueryList, fmt.Sprintf("%v=%v", k, v)) + } + uri = uri + "?" + strings.Join(rawQueryList, "&") + } else { + if jsonByte, err := json.Marshal(param); err != nil { + t.Error(err) + return + } else { + body = bytes.NewReader(jsonByte) + } } - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { - t.Error(err) - } + req := httptest.NewRequest(method, uri, body) - if response.Content != "Hello" { - t.Errorf("content not equal: \"%s\"", response.Content) + for k, v := range header { + req.Header.Set(k, v) } - if response.Lang != "plain" { - t.Errorf("lang not equal: \"%s\"", response.Lang) - } -} - -func TestPermanentPost(t *testing.T) { - body := request.Set(t, router, "", "plain", "Hello", "") - - type JsonResponse struct { - Key uint64 `json:"key"` - } - - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + if data, err := ioutil.ReadAll(w.Result().Body); err != nil { t.Error(err) - } - - keyP = response.Key -} - -func TestPermanentGet(t *testing.T) { - body := request.Get(t, router, util.Uint2string(uint64(keyP)), "") - checkGetResponse(t, body) + return + } else { + if err := json.Unmarshal(data, &result); err != nil { + t.Error(err) + return + } + } + return } -func TestTemporaryPost(t *testing.T) { - body := request.Set(t, router, "example", "plain", "Hello", "") - type JsonResponse struct { - Key string `json:"key"` - } - - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { - t.Error(err) - } - - keyT = response.Key -} - -func TestTemporaryGet(t *testing.T) { - body := request.Get(t, router, keyT, "") - checkGetResponse(t, body) -} - -func TestReadOncePost(t *testing.T) { - body := request.Set(t, router, "once", "plain", "Hello", "") - - type JsonResponse struct { - Key string `json:"key"` - } - - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { - t.Error(err) - } - - keyR = response.Key -} - -func TestReadOnceGet(t *testing.T) { - body := request.Get(t, router, keyR, "") - checkGetResponse(t, body) -} - -func TestPermanentPasswordPost(t *testing.T) { - body := request.Set(t, router, "", "plain", "Hello", "password") - - type JsonResponse struct { - Key uint64 `json:"key"` - } - - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { - t.Error(err) - } - - keyP = response.Key +type testCase struct { + name string + method string + uri string + param map[string]interface{} + response map[string]interface{} + expect map[string]interface{} } -func TestPermanentPasswordGet(t *testing.T) { - body := request.Get(t, router, util.Uint2string(uint64(keyP)), "password") - checkGetResponse(t, body) +// TODO permanent test case + +func makeCreateTestCase() (result map[string]*testCase) { + result = make(map[string]*testCase) + // create paste + for _, pasteType := range []string{"temporary"} { + var ( + method = "POST" + status = 201 + ) + + if pasteType == "permanent" { + status = 401 + } + + for _, password := range []string{"", "_with_password"} { + name := method + "_" + pasteType + password + result[name] = &testCase{ + name, method, "/api/v3/paste/", + map[string]interface{}{ + "lang": "plain", + "content": "Hello World!", + }, + map[string]interface{}{}, + map[string]interface{}{ + "status": status, + }, + } + + if pasteType == "temporary" { + result[name].param["self_destruct"] = true + result[name].param["expire_minute"] = 5 + result[name].param["expire_count"] = 1 + } + } + } + return } -func TestTemporaryPasswordPost(t *testing.T) { - body := request.Set(t, router, "example", "plain", "Hello", "password") - - type JsonResponse struct { - Key string `json:"key"` - } - - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { - t.Error(err) - } - - keyT = response.Key +func makeGetTestCase(createCaseList map[string]*testCase) (result map[string]*testCase) { + result = make(map[string]*testCase) + // get paste + for _, pasteType := range []string{"temporary"} { + method := "GET" + for _, password := range []string{"", "_with_password"} { + name := method + "_" + pasteType + password + previousName := "POST_" + pasteType + password + result[name] = &testCase{ + name, method, "/api/v3/paste/" + (createCaseList[previousName].response["key"]).(string), + map[string]interface{}{ + "password": password, + }, + map[string]interface{}{}, + map[string]interface{}{ + "status": 200, + "lang": "plain", + "content": "Hello World!", + }, + } + } + } + return } -func TestTemporaryPasswordGet(t *testing.T) { - body := request.Get(t, router, keyT, "password") - checkGetResponse(t, body) +func equal(expect interface{}, value interface{}) bool { + return fmt.Sprintf("%v", expect) == fmt.Sprintf("%v", value) } -func TestReadOncePasswordPost(t *testing.T) { - body := request.Set(t, router, "once", "plain", "Hello", "password") - - type JsonResponse struct { - Key string `json:"key"` - } +func test(t *testing.T, caseList map[string]*testCase) { + for name, c := range caseList { + t.Run(name, func(t *testing.T) { + c.response = request(t, c.method, c.uri, c.param, map[string]string{"Accept": "application/json"}) - response := JsonResponse{} - if err := json.Unmarshal(body, &response); err != nil { - t.Error(err) + for k, v := range c.expect { + if !equal(v, c.response[k]) { + t.Errorf("check field \"%s\" failed, expect %v, got %v", k, v, c.response[k]) + } + } + }) } - - keyR = response.Key } -func TestReadOncePasswordGet(t *testing.T) { - body := request.Get(t, router, keyR, "password") - checkGetResponse(t, body) +func Test(t *testing.T) { + createCaseList := makeCreateTestCase() + test(t, createCaseList) + getCaseList := makeGetTestCase(createCaseList) + test(t, getCaseList) } -func TestExist(t *testing.T) { - if model.Exist(keyT) { - t.Errorf("test temporary key: %s failed.", keyT) - } - - if model.Exist(keyR) { - t.Errorf("test once key: %s failed.", keyR) - } - - TestTemporaryPost(t) - if !model.Exist(keyT) { - t.Errorf("test temporary key: %s failed.", keyT) - } - - TestTemporaryGet(t) - if model.Exist(keyT) { - t.Errorf("test temporary key: %s failed.", keyT) - } - - TestReadOncePost(t) - if !model.Exist(keyR) { - t.Errorf("test once key: %s failed.", keyR) - } - - TestReadOnceGet(t) - if model.Exist(keyR) { - t.Errorf("test once key: %s failed.", keyR) - } - - TestTemporaryPasswordPost(t) - if !model.Exist(keyT) { - t.Errorf("test temporary key: %s failed.", keyT) - } - - TestTemporaryPasswordGet(t) - if model.Exist(keyT) { - t.Errorf("test temporary key: %s failed.", keyT) - } - - TestReadOncePasswordPost(t) - if !model.Exist(keyR) { - t.Errorf("test once key: %s failed.", keyR) - } - - TestReadOncePasswordGet(t) - if model.Exist(keyR) { - t.Errorf("test once key: %s failed.", keyR) - } +func TestMain(m *testing.M) { + m.Run() } diff --git a/tests/request/request.go b/tests/request/request.go deleted file mode 100644 index c7ccda3..0000000 --- a/tests/request/request.go +++ /dev/null @@ -1,65 +0,0 @@ -package request - -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/gin-gonic/gin" - "io/ioutil" - "net/http/httptest" - "testing" -) - -var baseUrl = "/api/v2/" - -// Get 根据特定请求 uri,发起 get 请求返回响应 -func get(t *testing.T, uri string, router *gin.Engine) []byte { - req := httptest.NewRequest("GET", uri, nil) // 构造get请求 - w := httptest.NewRecorder() // 初始化响应 - router.ServeHTTP(w, req) // 调用相应的handler接口 - result := w.Result() // 提取响应 - body, err := ioutil.ReadAll(result.Body) // 读取响应body - if err != nil { - t.Error(err) - } - return body -} - -// requestJson 根据特定请求 uri 和参数 param,以 Json 形式传递参数,发起 post 请求返回响应 -func requestJson(t *testing.T, method string, uri string, param map[string]interface{}, router *gin.Engine) []byte { - jsonByte, err := json.Marshal(param) // 将参数转化为 json 比特流 - if err != nil { - t.Error(err) - } - req := httptest.NewRequest(method, uri, bytes.NewReader(jsonByte)) // 构造post请求,json数据以请求body的形式传递 - w := httptest.NewRecorder() // 初始化响应 - router.ServeHTTP(w, req) // 调用相应的handler接口 - result := w.Result() // 提取响应 - body, err := ioutil.ReadAll(result.Body) // 读取响应body - if err != nil { - t.Error(err) - } - return body -} - -func Set(t *testing.T, router *gin.Engine, Key string, Lang string, Content string, Password string) []byte { - uri := baseUrl + Key - if Key == "" || Key == "once" { - params := make(map[string]interface{}) - params["lang"] = Lang - params["content"] = Content - params["password"] = Password - return requestJson(t, "POST", uri, params, router) - } else { - params := make(map[string]interface{}) - params["lang"] = Lang - params["content"] = Content - params["password"] = Password - return requestJson(t, "PUT", uri, params, router) - } -} - -func Get(t *testing.T, router *gin.Engine, Key string, Password string) []byte { - uri := fmt.Sprintf("%s%s,%s?json", baseUrl, Key, Password) - return get(t, uri, router) -} diff --git a/v2/handler/handler.go b/v2/handler/handler.go deleted file mode 100644 index 89a82ac..0000000 --- a/v2/handler/handler.go +++ /dev/null @@ -1,248 +0,0 @@ -package handler - -import ( - "fmt" - dao "github.com/PasteUs/PasteMeGoBackend/model/paste" - "github.com/PasteUs/PasteMeGoBackend/util" - "github.com/PasteUs/PasteMeGoBackend/v2/model" - "github.com/gin-gonic/gin" - "github.com/jinzhu/gorm" - "github.com/wonderivan/logger" - "net/http" - "strings" -) - -// PermanentCreator 创建一个永久的 Paste, key 是自增键 -func PermanentCreator(requests *gin.Context) { - IP := requests.ClientIP() // 用户 IP - paste := model.Permanent{ - AbstractPaste: &model.AbstractPaste{ - ClientIP: IP, - }, - } - // 绑定请求参数 - if err := requests.ShouldBindJSON(&paste); err != nil { - logger.Error(util.LogFormat(IP, "Bind failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": "bind failed", - "error": err.Error(), - }) - } else { - if err := paste.Save(); err != nil { - logger.Error(util.LogFormat(IP, "Save failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "message": "save failed", - "error": err.Error(), - }) - } else { - logger.Info(util.LogFormat(IP, "Create an permanent paste with key: %s", util.Uint2string(paste.Key))) - - requests.JSON(http.StatusCreated, gin.H{ - "status": http.StatusCreated, - "key": paste.Key, - }) - } - } -} - -// TemporaryCreator 创建一个阅后即焚的 Paste, key 是指定的 -func TemporaryCreator(requests *gin.Context) { - IP, key := requests.ClientIP(), requests.Param("key") - key = strings.ToLower(key) // 进行大写到小写的转换 - table, err := util.ValidChecker(key) - if err != nil { - if err.Error() == "wrong length" { - logger.Warn(util.LogFormat(IP, "Trying to create temporary paste with key: %s", key)) - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusBadRequest, - "error": err.Error(), - "message": "key's length should at least 3 and at most 8", - }) - } else { - logger.Warn(util.LogFormat(IP, "Trying to create temporary paste with key: %s", key)) - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusBadRequest, - "error": err.Error(), - "message": "temporary key should only contains digits and lowercase letters, at least one alpha is required", - }) - } - } else { - if table != "temporary" { - logger.Warn(util.LogFormat(IP, "Trying to create temporary paste with key: %s", key)) - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusBadRequest, - "error": "wrong key type", - "message": "temporary key should only contains digits and lowercase letters, at least one alpha is required", - }) - } else { - paste := model.Temporary{ - Key: key, - AbstractPaste: &model.AbstractPaste{ - ClientIP: requests.ClientIP(), - }, - } - if err := requests.ShouldBindJSON(&paste); err != nil { - logger.Error(util.LogFormat(IP, "Bind failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": err.Error(), - "message": "bind failed", - }) - } else { - if err := paste.Save(); err != nil { - logger.Error(util.LogFormat(IP, "Save failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": err.Error(), - "message": "save failed", - }) - } else { - logger.Info(util.LogFormat(IP, "Create an temporary paste with key: %s", paste.Key)) - requests.JSON(http.StatusCreated, gin.H{ - "status": http.StatusCreated, - "key": paste.Key, - }) - } - } - } - } -} - -// ReadOnceCreator 创建一个阅后即焚的 Paste, key 是随机的 -func ReadOnceCreator(requests *gin.Context) { - IP := requests.ClientIP() - paste := model.Temporary{ - Key: dao.Generator(8, false, &model.Temporary{}), - AbstractPaste: &model.AbstractPaste{ - ClientIP: IP, - }, - } - if err := requests.ShouldBindJSON(&paste); err != nil { - logger.Error(util.LogFormat(IP, "Bind failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": err.Error(), - "message": "bind failed", - }) - } else { - if err := paste.Save(); err != nil { - logger.Error(util.LogFormat(IP, "Save failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": err.Error(), - "message": "save failed", - }) - } else { - logger.Info(util.LogFormat(IP, "Create an once paste with key: %s", paste.Key)) - requests.JSON(http.StatusCreated, gin.H{ - "status": http.StatusCreated, - "key": paste.Key, - }) - } - } -} - -// Query 访问未加密的 Paste, token 为 -// 访问加密的 Paste, token 为 , -func Query(requests *gin.Context) { - IP, token := requests.ClientIP(), requests.Param("token") - if token == "" { // 空的 token - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusBadRequest, - "error": "empty token", - "message": "wrong params", - }) - } else { - key, password := util.Parse(token) // 分离出 key 和 password - key = strings.ToLower(key) // 进行大写到小写的转换 - table, err := util.ValidChecker(key) // 正则匹配 - - if err != nil { - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusBadRequest, - "error": err.Error(), - "message": "request key not valid", - }) - } else { - var paste model.IPaste - if table == "temporary" { - paste = &model.Temporary{Key: key} - } else { - paste = &model.Permanent{Key: util.String2uint(key)} - } - - if err := paste.Get(); err != nil { - if err == gorm.ErrRecordNotFound { - logger.Info(util.LogFormat(IP, "Access empty key: %s", key)) - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusNotFound, - "error": err.Error(), - "message": fmt.Sprintf("key: %s not found", key), - }) - } else { - logger.Info(util.LogFormat(IP, "Query from db failed: %s", err.Error())) - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": err.Error(), - "message": "query from db failed", - }) - } - } else { - if paste.GetPassword() == "" || paste.GetPassword() == util.String2md5(password) { // 密码为空或者密码正确 - logger.Info(util.LogFormat(IP, "Password accept")) - if table == "temporary" { - if err := paste.Delete(); err != nil { - requests.JSON(http.StatusInternalServerError, gin.H{ - "status": http.StatusInternalServerError, - "error": err.Error(), - "message": fmt.Sprintf("key: %s delete failed", key), - }) - return - } - } - - jsonRequest := requests.DefaultQuery("json", "false") - if jsonRequest == "false" { // raw request - logger.Info(util.LogFormat(IP, "jsonRequest: false")) - requests.String(http.StatusOK, paste.GetContent()) - } else { // json request - logger.Info(util.LogFormat(IP, "jsonRequest: true")) - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusOK, - "lang": paste.GetLang(), - "content": paste.GetContent(), - }) - } - } else { - logger.Info(util.LogFormat(IP, "Password wrong")) // 密码错误 - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusUnauthorized, - "error": "wrong password", - "message": "wrong password", - }) - } - } - } - } -} - -func NotFoundHandler(requests *gin.Context) { - requests.JSON(http.StatusNotFound, gin.H{ - "status": http.StatusNotFound, - "error": "not found", - "message": "no router founded", - }) -} - -func Beat(requests *gin.Context) { - method := requests.DefaultQuery("method", "none") - if method == "beat" { - requests.JSON(http.StatusOK, gin.H{ - "status": http.StatusOK, - }) - } else { - NotFoundHandler(requests) - } -} diff --git a/v2/model/paste.go b/v2/model/paste.go deleted file mode 100644 index 2bf70a7..0000000 --- a/v2/model/paste.go +++ /dev/null @@ -1,92 +0,0 @@ -package model - -import ( - "errors" - "github.com/PasteUs/PasteMeGoBackend/config" - "github.com/PasteUs/PasteMeGoBackend/model/dao" - "github.com/PasteUs/PasteMeGoBackend/util" - "github.com/wonderivan/logger" - "time" -) - -func init() { - if config.Config.Database.Type != "mysql" { - if !dao.DB.HasTable(&Permanent{}) { - logger.Warn("Table permanents not found, start creating") - if err := dao.DB.CreateTable(&Permanent{}).Error; err != nil { - logger.Painc("Create table permanents failed: " + err.Error()) - } - dao.DB.Exec("INSERT INTO `sqlite_sequence` (`name`, `seq`) VALUES ('permanents', 99)") - } - - if !dao.DB.HasTable(&Temporary{}) { - logger.Warn("Table temporaries not found, start creating") - if err := dao.DB.CreateTable(&Temporary{}).Error; err != nil { - logger.Painc("Create table temporaries failed: " + err.Error()) - } - } - } else { - if !dao.DB.HasTable(&Permanent{}) { - logger.Warn("Table permanents not found, start creating") - if err := dao.DB.Set( - "gorm:table_options", - "ENGINE=Innodb DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=100", - ).CreateTable(&Permanent{}).Error; err != nil { - logger.Painc("Create table permanents failed: " + err.Error()) - } - } - - if !dao.DB.HasTable(&Temporary{}) { - logger.Warn("Table temporaries not found, start creating") - if err := dao.DB.Set( - "gorm:table_options", - "ENGINE=Innodb DEFAULT CHARSET=utf8mb4", - ).CreateTable(&Temporary{}).Error; err != nil { - logger.Painc("Create table temporaries failed: " + err.Error()) - } - } - } -} - -type IPaste interface { - Save() error - Get() error - Delete() error - GetContent() string - GetLang() string - GetPassword() string -} - -type AbstractPaste struct { - IPaste - Lang string `json:"lang" gorm:"type:varchar(16)"` // 语言类型 - Content string `json:"content" gorm:"type:mediumtext"` // 内容,最大长度为 16777215(2^24-1) 个字符 - Password string `json:"password" gorm:"type:varchar(32)"` // 密码 - ClientIP string `gorm:"type:varchar(64)"` // 用户 IP - CreatedAt time.Time // 存储记录的创建时间 -} - -func (paste *AbstractPaste) GetContent() string { - return paste.Content -} - -func (paste *AbstractPaste) GetPassword() string { - return paste.Password -} - -func (paste *AbstractPaste) GetLang() string { - return paste.Lang -} - -func (paste *AbstractPaste) beforeSave() error { - if paste.Content == "" { - return errors.New("empty content") // 内容为空,返回错误信息 "empty content" - } - if paste.Lang == "" { - return errors.New("empty lang") // 语言类型为空,返回错误信息 "empty lang" - } - if paste.Password != "" { - paste.Password = util.String2md5(paste.Password) // 加密存储,设置密码 - } - return nil -} diff --git a/v2/model/permanent.go b/v2/model/permanent.go deleted file mode 100644 index f0f7afe..0000000 --- a/v2/model/permanent.go +++ /dev/null @@ -1,33 +0,0 @@ -package model - -import ( - "github.com/PasteUs/PasteMeGoBackend/model/dao" - "time" -) - -// Permanent 永久 -type Permanent struct { - Key uint64 `gorm:"primary_key"` // 主键:索引 - *AbstractPaste - // 存储记录的删除时间 - // 删除具有 DeletedAt 字段的记录,它不会从数据库中删除,但只将字段 DeletedAt 设置为当前时间,并在查询时无法找到记录 - DeletedAt *time.Time -} - -// Save 成员函数,创建 -func (paste *Permanent) Save() error { - if err := paste.beforeSave(); err != nil { - return err - } - return dao.DB.Create(&paste).Error -} - -// Delete 成员函数,删除 -func (paste *Permanent) Delete() error { - return dao.DB.Delete(&paste).Error -} - -// Get 成员函数,访问 -func (paste *Permanent) Get() error { - return dao.DB.First(&paste).Error -} diff --git a/v2/model/temporary.go b/v2/model/temporary.go deleted file mode 100644 index 7b6df4c..0000000 --- a/v2/model/temporary.go +++ /dev/null @@ -1,33 +0,0 @@ -package model - -import "github.com/PasteUs/PasteMeGoBackend/model/dao" - -// Temporary 临时 -type Temporary struct { - Key string `json:"key" gorm:"type:varchar(16);primary_key"` // 主键:索引 - *AbstractPaste -} - -// Save 成员函数,保存 -func (paste *Temporary) Save() error { - if err := paste.beforeSave(); err != nil { - return err - } - return dao.DB.Create(&paste).Error -} - -// Delete 成员函数,删除 -func (paste *Temporary) Delete() error { - return dao.DB.Delete(&paste).Error -} - -// Get 成员函数,查看 -func (paste *Temporary) Get() error { - return dao.DB.Find(&paste).Error -} - -func Exist(key string) bool { - count := uint8(0) - dao.DB.Model(&Temporary{}).Where("`key` = ?", key).Count(&count) - return count > 0 -}