diff --git a/core/.editorconfig b/core/.editorconfig new file mode 100644 index 00000000..45a3e334 --- /dev/null +++ b/core/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.yaml,*.yml,package.json}] +indent_size = 2 +indent_style = space diff --git a/core/.github/workflows/test.yml b/core/.github/workflows/test.yml new file mode 100644 index 00000000..945d03e7 --- /dev/null +++ b/core/.github/workflows/test.yml @@ -0,0 +1,36 @@ +name: "Test" + +on: + push: + branches: [ main, develop ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main, develop ] + +jobs: + test: + name: Test + runs-on: ubuntu-20.04 + services: + mongo: + image: mongo:5 + ports: + - 27017:27017 + env: + CRAWLAB_SERVER_PORT: 9999 + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - uses: actions/setup-go@v3 + with: + go-version: '^1.22' + - name: Run unit tests + run: | + mods=(\ + "github.com/crawlab-team/crawlab/core/controllers" \ + "github.com/crawlab-team/crawlab/core/models/client" \ + "github.com/crawlab-team/crawlab/core/models/service" \ + ) + for pkg in ${mods[@]}; do + go test ${pkg} + done diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 00000000..53fc5181 --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,10 @@ +.idea +.DS_Store +vendor/ +tmp/ +build/ +dist/ +*.log +gen/ +*.exe +*.txt diff --git a/core/LICENSE b/core/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/core/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/core/README.md b/core/README.md new file mode 100644 index 00000000..275133a5 --- /dev/null +++ b/core/README.md @@ -0,0 +1,2 @@ +# crawlab-core +Backend core modules for Crawlab diff --git a/core/apps/api.go b/core/apps/api.go new file mode 100644 index 00000000..94e887c3 --- /dev/null +++ b/core/apps/api.go @@ -0,0 +1,126 @@ +package apps + +import ( + "context" + "errors" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/middlewares" + "github.com/crawlab-team/crawlab/core/routes" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "net" + "net/http" + "time" +) + +func init() { + // set gin mode + if viper.GetString("gin.mode") == "" { + gin.SetMode(gin.ReleaseMode) + } else { + gin.SetMode(viper.GetString("gin.mode")) + } +} + +type Api struct { + // dependencies + interfaces.WithConfigPath + + // internals + app *gin.Engine + ln net.Listener + srv *http.Server + ready bool +} + +func (app *Api) Init() { + // initialize controllers + _ = initModule("controllers", controllers.InitControllers) + + // initialize middlewares + _ = app.initModuleWithApp("middlewares", middlewares.InitMiddlewares) + + // initialize routes + _ = app.initModuleWithApp("routes", routes.InitRoutes) +} + +func (app *Api) Start() { + // address + host := viper.GetString("server.host") + port := viper.GetString("server.port") + address := net.JoinHostPort(host, port) + + // http server + app.srv = &http.Server{ + Handler: app.app, + Addr: address, + } + + // listen + var err error + app.ln, err = net.Listen("tcp", address) + if err != nil { + panic(err) + } + app.ready = true + + // serve + if err := http.Serve(app.ln, app.app); err != nil { + if !errors.Is(err, http.ErrServerClosed) { + log.Error("run server error:" + err.Error()) + } else { + log.Info("server graceful down") + } + } +} + +func (app *Api) Wait() { + DefaultWait() +} + +func (app *Api) Stop() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := app.srv.Shutdown(ctx); err != nil { + log.Error("run server error:" + err.Error()) + } +} + +func (app *Api) GetGinEngine() *gin.Engine { + return app.app +} + +func (app *Api) GetHttpServer() *http.Server { + return app.srv +} + +func (app *Api) Ready() (ok bool) { + return app.ready +} + +func (app *Api) initModuleWithApp(name string, fn func(app *gin.Engine) error) (err error) { + return initModule(name, func() error { + return fn(app.app) + }) +} + +func NewApi() *Api { + api := &Api{ + app: gin.New(), + } + api.Init() + return api +} + +var api *Api + +func GetApi() *Api { + if api != nil { + return api + } + api = NewApi() + return api +} diff --git a/core/apps/api_v2.go b/core/apps/api_v2.go new file mode 100644 index 00000000..966b1a08 --- /dev/null +++ b/core/apps/api_v2.go @@ -0,0 +1,122 @@ +package apps + +import ( + "context" + "errors" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/middlewares" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "net" + "net/http" + "time" +) + +func init() { + // set gin mode + if viper.GetString("gin.mode") == "" { + gin.SetMode(gin.ReleaseMode) + } else { + gin.SetMode(viper.GetString("gin.mode")) + } +} + +type ApiV2 struct { + // dependencies + interfaces.WithConfigPath + + // internals + app *gin.Engine + ln net.Listener + srv *http.Server + ready bool +} + +func (app *ApiV2) Init() { + // initialize middlewares + _ = app.initModuleWithApp("middlewares", middlewares.InitMiddlewares) + + // initialize routes + _ = app.initModuleWithApp("routes", controllers.InitRoutes) +} + +func (app *ApiV2) Start() { + // address + host := viper.GetString("server.host") + port := viper.GetString("server.port") + address := net.JoinHostPort(host, port) + + // http server + app.srv = &http.Server{ + Handler: app.app, + Addr: address, + } + + // listen + var err error + app.ln, err = net.Listen("tcp", address) + if err != nil { + panic(err) + } + app.ready = true + + // serve + if err := http.Serve(app.ln, app.app); err != nil { + if !errors.Is(err, http.ErrServerClosed) { + log.Error("run server error:" + err.Error()) + } else { + log.Info("server graceful down") + } + } +} + +func (app *ApiV2) Wait() { + DefaultWait() +} + +func (app *ApiV2) Stop() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := app.srv.Shutdown(ctx); err != nil { + log.Error("run server error:" + err.Error()) + } +} + +func (app *ApiV2) GetGinEngine() *gin.Engine { + return app.app +} + +func (app *ApiV2) GetHttpServer() *http.Server { + return app.srv +} + +func (app *ApiV2) Ready() (ok bool) { + return app.ready +} + +func (app *ApiV2) initModuleWithApp(name string, fn func(app *gin.Engine) error) (err error) { + return initModule(name, func() error { + return fn(app.app) + }) +} + +func NewApiV2() *ApiV2 { + api := &ApiV2{ + app: gin.New(), + } + api.Init() + return api +} + +var apiV2 *ApiV2 + +func GetApiV2() *ApiV2 { + if apiV2 != nil { + return apiV2 + } + apiV2 = NewApiV2() + return apiV2 +} diff --git a/core/apps/docker.go b/core/apps/docker.go new file mode 100644 index 00000000..948a5cff --- /dev/null +++ b/core/apps/docker.go @@ -0,0 +1,199 @@ +package apps + +import ( + "bufio" + "fmt" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/sys_exec" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/imroc/req" + "github.com/spf13/viper" + "os" + "os/exec" + "strings" + "time" +) + +type Docker struct { + // parent + parent ServerApp + + // dependencies + interfaces.WithConfigPath + + // seaweedfs log + fsLogFilePath string + fsLogFile *os.File + fsReady bool +} + +func (app *Docker) Init() { + var err error + app.fsLogFile, err = os.OpenFile(app.fsLogFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, os.FileMode(0777)) + if err != nil { + trace.PrintError(err) + } + + // replace paths + if err := app.replacePaths(); err != nil { + panic(err) + } + + // start nginx + go app.startNginx() + + // start seaweedfs + go app.startSeaweedFs() +} + +func (app *Docker) Start() { + // import demo + //if utils.IsDemo() && utils.InitializedDemo() { + // go app.importDemo() + //} +} + +func (app *Docker) Wait() { + DefaultWait() +} + +func (app *Docker) Stop() { +} + +func (app *Docker) GetParent() (parent ServerApp) { + return app.parent +} + +func (app *Docker) SetParent(parent ServerApp) { + app.parent = parent +} + +func (app *Docker) Ready() (ok bool) { + return app.fsReady && + app.parent.GetApi().Ready() +} + +func (app *Docker) replacePaths() (err error) { + // read + indexHtmlPath := "/app/dist/index.html" + indexHtmlBytes, err := os.ReadFile(indexHtmlPath) + if err != nil { + return trace.TraceError(err) + } + indexHtml := string(indexHtmlBytes) + + // replace paths + baseUrl := viper.GetString("base.url") + if baseUrl != "" { + indexHtml = app._replacePath(indexHtml, "js", baseUrl) + indexHtml = app._replacePath(indexHtml, "css", baseUrl) + indexHtml = app._replacePath(indexHtml, " d.svc.GetMany:start") + list, err := d.svc.GetList(nil, &mongo.FindOptions{ + Sort: bson.D{{"_id", -1}}, + }) + if err != nil { + if err == mongo2.ErrNoDocuments { + HandleErrorNotFound(c, err) + } else { + HandleErrorInternalServerError(c, err) + } + return + } + log.Debugf("getAll -> d.svc.GetMany:end. elapsed: %d ms", time.Now().Sub(tic).Milliseconds()) + tic = time.Now() + + // total count + tic = time.Now() + log.Debugf("getAll -> d.svc.Count:start") + total, err := d.svc.Count(nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + log.Debugf("getAll -> d.svc.Count:end. elapsed: %d ms", time.Now().Sub(tic).Milliseconds()) + + // response + HandleSuccessWithListData(c, list, total) +} + +func (d *ListControllerDelegate) getList(c *gin.Context) (l interfaces.List, total int, err error) { + // params + pagination := MustGetPagination(c) + query := MustGetFilterQuery(c) + sort := MustGetSortOption(c) + + // get list + l, err = d.svc.GetList(query, &mongo.FindOptions{ + Sort: sort, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + HandleSuccessWithListData(c, nil, 0) + } else { + HandleErrorInternalServerError(c, err) + } + return + } + + // total count + total, err = d.svc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + return l, total, nil +} diff --git a/core/controllers/delegate_list_action.go b/core/controllers/delegate_list_action.go new file mode 100644 index 00000000..deacf347 --- /dev/null +++ b/core/controllers/delegate_list_action.go @@ -0,0 +1,17 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" +) + +func NewListPostActionControllerDelegate(id ControllerId, svc interfaces.ModelBaseService, actions []Action) (d *ListActionControllerDelegate) { + return &ListActionControllerDelegate{ + NewListControllerDelegate(id, svc), + NewActionControllerDelegate(id, actions), + } +} + +type ListActionControllerDelegate struct { + ListController + ActionController +} diff --git a/core/controllers/demo.go b/core/controllers/demo.go new file mode 100644 index 00000000..9c25aab4 --- /dev/null +++ b/core/controllers/demo.go @@ -0,0 +1,73 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "net/http" +) + +func getDemoActions() []Action { + ctx := newDemoContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/import", + HandlerFunc: ctx.import_, + }, + { + Method: http.MethodGet, + Path: "/reimport", + HandlerFunc: ctx.reimport, + }, + { + Method: http.MethodGet, + Path: "/cleanup", + HandlerFunc: ctx.cleanup, + }, + } +} + +type demoContext struct { +} + +func (ctx *demoContext) import_(c *gin.Context) { + if err := utils.ImportDemo(); err != nil { + trace.PrintError(err) + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *demoContext) reimport(c *gin.Context) { + if err := utils.ReimportDemo(); err != nil { + trace.PrintError(err) + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *demoContext) cleanup(c *gin.Context) { + if err := utils.ReimportDemo(); err != nil { + trace.PrintError(err) + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +var _demoCtx *demoContext + +func newDemoContext() *demoContext { + if _demoCtx != nil { + return _demoCtx + } + + _demoCtx = &demoContext{} + + return _demoCtx +} + +var DemoController ActionController diff --git a/core/controllers/environment.go b/core/controllers/environment.go new file mode 100644 index 00000000..560b02c9 --- /dev/null +++ b/core/controllers/environment.go @@ -0,0 +1,57 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/service" +) + +var EnvironmentController *environmentController + +var EnvironmentActions []Action + +type environmentController struct { + ListActionControllerDelegate + d ListActionControllerDelegate + ctx *environmentContext +} + +type environmentContext struct { + modelSvc service.ModelService + userSvc interfaces.UserService +} + +func newEnvironmentContext() *environmentContext { + // context + ctx := &environmentContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + userSvc interfaces.UserService, + ) { + ctx.modelSvc = modelSvc + ctx.userSvc = userSvc + }); err != nil { + panic(err) + } + + return ctx +} + +func newEnvironmentController() *environmentController { + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListPostActionControllerDelegate(ControllerIdEnvironment, modelSvc.GetBaseService(interfaces.ModelIdEnvironment), EnvironmentActions) + d := NewListPostActionControllerDelegate(ControllerIdEnvironment, modelSvc.GetBaseService(interfaces.ModelIdEnvironment), EnvironmentActions) + ctx := newEnvironmentContext() + + return &environmentController{ + ListActionControllerDelegate: *ctr, + d: *d, + ctx: ctx, + } +} diff --git a/core/controllers/export.go b/core/controllers/export.go new file mode 100644 index 00000000..b99ffcb9 --- /dev/null +++ b/core/controllers/export.go @@ -0,0 +1,127 @@ +package controllers + +import ( + "errors" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/export" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/gin-gonic/gin" + "net/http" +) + +var ExportController ActionController + +func getExportActions() []Action { + ctx := newExportContext() + return []Action{ + { + Method: http.MethodPost, + Path: "/:type", + HandlerFunc: ctx.postExport, + }, + { + Method: http.MethodGet, + Path: "/:type/:id", + HandlerFunc: ctx.getExport, + }, + { + Method: http.MethodGet, + Path: "/:type/:id/download", + HandlerFunc: ctx.getExportDownload, + }, + } +} + +type exportContext struct { + csvSvc interfaces.ExportService + jsonSvc interfaces.ExportService +} + +func (ctx *exportContext) postExport(c *gin.Context) { + exportType := c.Param("type") + exportTarget := c.Query("target") + exportFilter, _ := GetFilter(c) + + var exportId string + var err error + switch exportType { + case constants.ExportTypeCsv: + exportId, err = ctx.csvSvc.Export(exportType, exportTarget, exportFilter) + case constants.ExportTypeJson: + exportId, err = ctx.jsonSvc.Export(exportType, exportTarget, exportFilter) + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + return + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, exportId) +} + +func (ctx *exportContext) getExport(c *gin.Context) { + exportType := c.Param("type") + exportId := c.Param("id") + + var exp interfaces.Export + var err error + switch exportType { + case constants.ExportTypeCsv: + exp, err = ctx.csvSvc.GetExport(exportId) + case constants.ExportTypeJson: + exp, err = ctx.jsonSvc.GetExport(exportId) + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, exp) +} + +func (ctx *exportContext) getExportDownload(c *gin.Context) { + exportType := c.Param("type") + exportId := c.Param("id") + + var exp interfaces.Export + var err error + switch exportType { + case constants.ExportTypeCsv: + exp, err = ctx.csvSvc.GetExport(exportId) + case constants.ExportTypeJson: + exp, err = ctx.jsonSvc.GetExport(exportId) + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + switch exportType { + case constants.ExportTypeCsv: + c.Header("Content-Type", "text/csv") + case constants.ExportTypeJson: + c.Header("Content-Type", "text/plain") + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", exp.GetDownloadPath())) + c.File(exp.GetDownloadPath()) +} + +func newExportContext() *exportContext { + return &exportContext{ + csvSvc: export.GetCsvService(), + jsonSvc: export.GetJsonService(), + } +} diff --git a/core/controllers/export_v2.go b/core/controllers/export_v2.go new file mode 100644 index 00000000..8fff7305 --- /dev/null +++ b/core/controllers/export_v2.go @@ -0,0 +1,87 @@ +package controllers + +import ( + "errors" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/export" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/gin-gonic/gin" +) + +func PostExport(c *gin.Context) { + exportType := c.Param("type") + exportTarget := c.Query("target") + exportFilter, _ := GetFilter(c) + + var exportId string + var err error + switch exportType { + case constants.ExportTypeCsv: + exportId, err = export.GetCsvService().Export(exportType, exportTarget, exportFilter) + case constants.ExportTypeJson: + exportId, err = export.GetJsonService().Export(exportType, exportTarget, exportFilter) + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + return + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, exportId) +} + +func GetExport(c *gin.Context) { + exportType := c.Param("type") + exportId := c.Param("id") + + var exp interfaces.Export + var err error + switch exportType { + case constants.ExportTypeCsv: + exp, err = export.GetCsvService().GetExport(exportId) + case constants.ExportTypeJson: + exp, err = export.GetJsonService().GetExport(exportId) + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, exp) +} + +func GetExportDownload(c *gin.Context) { + exportType := c.Param("type") + exportId := c.Param("id") + + var exp interfaces.Export + var err error + switch exportType { + case constants.ExportTypeCsv: + exp, err = export.GetCsvService().GetExport(exportId) + case constants.ExportTypeJson: + exp, err = export.GetJsonService().GetExport(exportId) + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + switch exportType { + case constants.ExportTypeCsv: + c.Header("Content-Type", "text/csv") + case constants.ExportTypeJson: + c.Header("Content-Type", "text/plain") + default: + HandleErrorBadRequest(c, errors.New(fmt.Sprintf("invalid export type: %s", exportType))) + } + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", exp.GetDownloadPath())) + c.File(exp.GetDownloadPath()) +} diff --git a/core/controllers/filer.go b/core/controllers/filer.go new file mode 100644 index 00000000..4af5d142 --- /dev/null +++ b/core/controllers/filer.go @@ -0,0 +1,130 @@ +package controllers + +import ( + "bufio" + "fmt" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/gin-gonic/gin" + "github.com/imroc/req" + "github.com/spf13/viper" + "net/http" + "strings" +) + +var FilerController ActionController + +func getFilerActions() []Action { + filerCtx := newFilerContext() + return []Action{ + { + Method: http.MethodGet, + Path: "*path", + HandlerFunc: filerCtx.do, + }, + { + Method: http.MethodPost, + Path: "*path", + HandlerFunc: filerCtx.do, + }, + { + Method: http.MethodPut, + Path: "*path", + HandlerFunc: filerCtx.do, + }, + { + Method: http.MethodDelete, + Path: "*path", + HandlerFunc: filerCtx.do, + }, + } +} + +type filerContext struct { + endpoint string +} + +func (ctx *filerContext) do(c *gin.Context) { + // request path + requestPath := strings.Replace(c.Request.URL.Path, "/filer", "", 1) + + // request url + requestUrl := fmt.Sprintf("%s%s", ctx.endpoint, requestPath) + if c.Request.URL.RawQuery != "" { + requestUrl += "?" + c.Request.URL.RawQuery + } + + // request body + bufR := bufio.NewScanner(c.Request.Body) + requestBody := req.BodyJSON(bufR.Bytes()) + + // request file uploads + var requestFileUploads []req.FileUpload + form, err := c.MultipartForm() + if err == nil { + for k, v := range form.File { + for _, fh := range v { + f, err := fh.Open() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + requestFileUploads = append(requestFileUploads, req.FileUpload{ + FileName: fh.Filename, + FieldName: k, + File: f, + }) + } + } + } + + // request header + requestHeader := req.Header{} + for k, v := range c.Request.Header { + if len(v) > 0 { + requestHeader[k] = v[0] + } + } + + // perform request + res, err := req.Do(c.Request.Method, requestUrl, requestHeader, requestBody, requestFileUploads) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // status code check + statusCode := res.Response().StatusCode + if statusCode == http.StatusNotFound { + HandleErrorNotFoundNoPrint(c, errors.ErrorControllerFilerNotFound) + return + } + + // response + for k, v := range res.Response().Header { + if len(v) > 0 { + c.Header(k, v[0]) + } + } + _, _ = c.Writer.Write(res.Bytes()) + c.AbortWithStatus(statusCode) +} + +var _filerCtx *filerContext + +func newFilerContext() *filerContext { + if _filerCtx != nil { + return _filerCtx + } + + ctx := &filerContext{ + endpoint: "http://localhost:8888", + } + + if viper.GetString("fs.filer.proxy") != "" { + ctx.endpoint = viper.GetString("fs.filer.proxy") + } + + _filerCtx = ctx + + return ctx +} diff --git a/core/controllers/filter.go b/core/controllers/filter.go new file mode 100644 index 00000000..143e4d13 --- /dev/null +++ b/core/controllers/filter.go @@ -0,0 +1,100 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "net/http" +) + +var FilterController ActionController + +func getFilterActions() []Action { + ctx := newFilterContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/:col", + HandlerFunc: ctx.getColFieldOptions, + }, + { + Method: http.MethodGet, + Path: "/:col/:value", + HandlerFunc: ctx.getColFieldOptions, + }, + { + Method: http.MethodGet, + Path: "/:col/:value/:label", + HandlerFunc: ctx.getColFieldOptions, + }, + } +} + +type filterContext struct { +} + +func (ctx *filterContext) getColFieldOptions(c *gin.Context) { + colName := c.Param("col") + value := c.Param("value") + if value == "" { + value = "_id" + } + label := c.Param("label") + if label == "" { + label = "name" + } + query := MustGetFilterQuery(c) + pipelines := mongo2.Pipeline{} + if query != nil { + pipelines = append(pipelines, bson.D{{"$match", query}}) + } + pipelines = append( + pipelines, + bson.D{ + { + "$group", + bson.M{ + "_id": bson.M{ + "value": "$" + value, + "label": "$" + label, + }, + }, + }, + }, + ) + pipelines = append( + pipelines, + bson.D{ + { + "$project", + bson.M{ + "value": "$_id.value", + "label": bson.M{"$toString": "$_id.label"}, + }, + }, + }, + ) + pipelines = append( + pipelines, + bson.D{ + { + "$sort", + bson.D{ + {"value", 1}, + }, + }, + }, + ) + var options []entity.FilterSelectOption + if err := mongo.GetMongoCol(colName).Aggregate(pipelines, nil).All(&options); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, options) +} + +func newFilterContext() *filterContext { + return &filterContext{} +} diff --git a/core/controllers/filter_v2.go b/core/controllers/filter_v2.go new file mode 100644 index 00000000..246750d0 --- /dev/null +++ b/core/controllers/filter_v2.go @@ -0,0 +1,69 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + mongo2 "go.mongodb.org/mongo-driver/mongo" +) + +func GetFilterColFieldOptions(c *gin.Context) { + colName := c.Param("col") + value := c.Param("value") + if value == "" { + value = "_id" + } + label := c.Param("label") + if label == "" { + label = "name" + } + query := MustGetFilterQuery(c) + pipelines := mongo2.Pipeline{} + if query != nil { + pipelines = append(pipelines, bson.D{{"$match", query}}) + } + pipelines = append( + pipelines, + bson.D{ + { + "$group", + bson.M{ + "_id": bson.M{ + "value": "$" + value, + "label": "$" + label, + }, + }, + }, + }, + ) + pipelines = append( + pipelines, + bson.D{ + { + "$project", + bson.M{ + "value": "$_id.value", + "label": bson.M{"$toString": "$_id.label"}, + }, + }, + }, + ) + pipelines = append( + pipelines, + bson.D{ + { + "$sort", + bson.D{ + {"value", 1}, + }, + }, + }, + ) + var options []entity.FilterSelectOption + if err := mongo.GetMongoCol(colName).Aggregate(pipelines, nil).All(&options); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, options) +} diff --git a/core/controllers/git.go b/core/controllers/git.go new file mode 100644 index 00000000..128323d4 --- /dev/null +++ b/core/controllers/git.go @@ -0,0 +1,3 @@ +package controllers + +var GitController ListController diff --git a/core/controllers/http.go b/core/controllers/http.go new file mode 100644 index 00000000..8aa5169b --- /dev/null +++ b/core/controllers/http.go @@ -0,0 +1,16 @@ +package controllers + +type Response[T any] struct { + Status string `json:"status"` + Message string `json:"message"` + Data T `json:"data"` + Error string `json:"error"` +} + +type ListResponse[T any] struct { + Status string `json:"status"` + Message string `json:"message"` + Total int `json:"total"` + Data []T `json:"data"` + Error string `json:"error"` +} diff --git a/core/controllers/init.go b/core/controllers/init.go new file mode 100644 index 00000000..e927b9c3 --- /dev/null +++ b/core/controllers/init.go @@ -0,0 +1,42 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/service" +) + +func InitControllers() (err error) { + modelSvc, err := service.GetService() + if err != nil { + return err + } + + NodeController = newNodeController() + ProjectController = newProjectController() + SpiderController = newSpiderController() + TaskController = newTaskController() + UserController = newUserController() + TagController = NewListControllerDelegate(ControllerIdTag, modelSvc.GetBaseService(interfaces.ModelIdTag)) + SettingController = newSettingController() + LoginController = NewActionControllerDelegate(ControllerIdLogin, getLoginActions()) + DataCollectionController = newDataCollectionController() + ResultController = NewActionControllerDelegate(ControllerIdResult, getResultActions()) + ScheduleController = newScheduleController() + StatsController = NewActionControllerDelegate(ControllerIdStats, getStatsActions()) + TokenController = newTokenController() + FilerController = NewActionControllerDelegate(ControllerIdFiler, getFilerActions()) + GitController = NewListControllerDelegate(ControllerIdGit, modelSvc.GetBaseService(interfaces.ModelIdGit)) + VersionController = NewActionControllerDelegate(ControllerIdVersion, getVersionActions()) + SystemInfoController = NewActionControllerDelegate(ControllerIdSystemInfo, getSystemInfoActions()) + DemoController = NewActionControllerDelegate(ControllerIdDemo, getDemoActions()) + RoleController = NewListControllerDelegate(ControllerIdRole, modelSvc.GetBaseService(interfaces.ModelIdRole)) + PermissionController = NewListControllerDelegate(ControllerIdPermission, modelSvc.GetBaseService(interfaces.ModelIdPermission)) + ExportController = NewActionControllerDelegate(ControllerIdExport, getExportActions()) + NotificationController = NewActionControllerDelegate(ControllerIdNotification, getNotificationActions()) + FilterController = NewActionControllerDelegate(ControllerIdFilter, getFilterActions()) + SyncController = NewActionControllerDelegate(ControllerIdSync, getSyncActions()) + DataSourceController = newDataSourceController() + EnvironmentController = newEnvironmentController() + + return nil +} diff --git a/core/controllers/login.go b/core/controllers/login.go new file mode 100644 index 00000000..42926fd5 --- /dev/null +++ b/core/controllers/login.go @@ -0,0 +1,64 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/gin-gonic/gin" + "net/http" +) + +var LoginController ActionController + +func getLoginActions() []Action { + loginCtx := newLoginContext() + return []Action{ + {Method: http.MethodPost, Path: "/login", HandlerFunc: loginCtx.login}, + {Method: http.MethodPost, Path: "/logout", HandlerFunc: loginCtx.logout}, + } +} + +type loginContext struct { + userSvc interfaces.UserService +} + +func (ctx *loginContext) login(c *gin.Context) { + var u models.User + if err := c.ShouldBindJSON(&u); err != nil { + HandleErrorBadRequest(c, err) + return + } + token, loggedInUser, err := ctx.userSvc.Login(&interfaces.UserLoginOptions{ + Username: u.Username, + Password: u.Password, + }) + if err != nil { + HandleErrorUnauthorized(c, errors.ErrorUserUnauthorized) + return + } + c.Set(constants.UserContextKey, loggedInUser) + HandleSuccessWithData(c, token) +} + +func (ctx *loginContext) logout(c *gin.Context) { + c.Set(constants.UserContextKey, nil) + HandleSuccess(c) +} + +func newLoginContext() *loginContext { + // context + ctx := &loginContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + userSvc interfaces.UserService, + ) { + ctx.userSvc = userSvc + }); err != nil { + panic(err) + } + + return ctx +} diff --git a/core/controllers/login_v2.go b/core/controllers/login_v2.go new file mode 100644 index 00000000..2bb08cef --- /dev/null +++ b/core/controllers/login_v2.go @@ -0,0 +1,36 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/user" + "github.com/gin-gonic/gin" +) + +func PostLogin(c *gin.Context) { + var payload struct { + Username string `json:"username"` + Password string `json:"password"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + userSvc, err := user.GetUserServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + token, loggedInUser, err := userSvc.Login(payload.Username, payload.Password) + if err != nil { + HandleErrorUnauthorized(c, errors.ErrorUserUnauthorized) + return + } + c.Set(constants.UserContextKey, loggedInUser) + HandleSuccessWithData(c, token) +} + +func PostLogout(c *gin.Context) { + c.Set(constants.UserContextKey, nil) + HandleSuccess(c) +} diff --git a/core/controllers/node.go b/core/controllers/node.go new file mode 100644 index 00000000..1ea748b2 --- /dev/null +++ b/core/controllers/node.go @@ -0,0 +1,94 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +var NodeController *nodeController + +type nodeController struct { + ListControllerDelegate +} + +func (ctr *nodeController) Post(c *gin.Context) { + var n models.Node + if err := c.ShouldBindJSON(&n); err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := ctr._post(c, &n); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctr *nodeController) PostList(c *gin.Context) { + // bind + var docs []models.Node + if err := c.ShouldBindJSON(&docs); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // success ids + var ids []primitive.ObjectID + + // iterate nodes + for _, n := range docs { + if err := ctr._post(c, &n); err != nil { + trace.PrintError(err) + continue + } + ids = append(ids, n.Id) + } + + // success + HandleSuccessWithData(c, docs) +} + +func (ctr *nodeController) _post(c *gin.Context, n *models.Node) (err error) { + // set default key + if n.Key == "" { + id, err := uuid.NewUUID() + if err != nil { + return trace.TraceError(err) + } + n.Key = id.String() + } + + // set default status + if n.Status == "" { + n.Status = constants.NodeStatusUnregistered + } + + // add + if err := delegate.NewModelDelegate(n, GetUserFromContext(c)).Add(); err != nil { + return trace.TraceError(err) + } + + return nil +} + +func newNodeController() *nodeController { + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListControllerDelegate(ControllerIdNode, modelSvc.GetBaseService(interfaces.ModelIdNode)) + + return &nodeController{ + ListControllerDelegate: *ctr, + } +} diff --git a/core/controllers/notification.go b/core/controllers/notification.go new file mode 100644 index 00000000..31b96c04 --- /dev/null +++ b/core/controllers/notification.go @@ -0,0 +1,158 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/notification" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" +) + +var NotificationController ActionController + +func getNotificationActions() []Action { + ctx := newNotificationContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/settings", + HandlerFunc: ctx.GetSettingList, + }, + { + Method: http.MethodGet, + Path: "/settings/:id", + HandlerFunc: ctx.GetSetting, + }, + { + Method: http.MethodPost, + Path: "/settings", + HandlerFunc: ctx.PostSetting, + }, + { + Method: http.MethodPut, + Path: "/settings/:id", + HandlerFunc: ctx.PutSetting, + }, + { + Method: http.MethodDelete, + Path: "/settings/:id", + HandlerFunc: ctx.DeleteSetting, + }, + { + Method: http.MethodPost, + Path: "/settings/:id/enable", + HandlerFunc: ctx.EnableSetting, + }, + { + Method: http.MethodPost, + Path: "/settings/:id/disable", + HandlerFunc: ctx.DisableSetting, + }, + } +} + +type notificationContext struct { + svc *notification.Service +} + +func (ctx *notificationContext) GetSettingList(c *gin.Context) { + query := MustGetFilterQuery(c) + pagination := MustGetPagination(c) + sort := MustGetSortOption(c) + res, total, err := ctx.svc.GetSettingList(query, pagination, sort) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithListData(c, res, total) +} + +func (ctx *notificationContext) GetSetting(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + res, err := ctx.svc.GetSetting(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, res) +} + +func (ctx *notificationContext) PostSetting(c *gin.Context) { + var s notification.NotificationSetting + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := ctx.svc.PosSetting(&s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *notificationContext) PutSetting(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + var s notification.NotificationSetting + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := ctx.svc.PutSetting(id, s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *notificationContext) DeleteSetting(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := ctx.svc.DeleteSetting(id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *notificationContext) EnableSetting(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := ctx.svc.EnableSetting(id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *notificationContext) DisableSetting(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := ctx.svc.DisableSetting(id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func newNotificationContext() *notificationContext { + ctx := ¬ificationContext{ + svc: notification.GetService(), + } + return ctx +} diff --git a/core/controllers/permission.go b/core/controllers/permission.go new file mode 100644 index 00000000..42be45dc --- /dev/null +++ b/core/controllers/permission.go @@ -0,0 +1,3 @@ +package controllers + +var PermissionController ListController diff --git a/core/controllers/project.go b/core/controllers/project.go new file mode 100644 index 00000000..fe13a9e8 --- /dev/null +++ b/core/controllers/project.go @@ -0,0 +1,103 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +var ProjectController *projectController + +type projectController struct { + ListControllerDelegate +} + +func (ctr *projectController) GetList(c *gin.Context) { + // get all if query field "all" is set true + all := MustGetFilterAll(c) + if all { + ctr.getAll(c) + return + } + + // get list + list, total, err := ctr.getList(c) + if err != nil { + return + } + data := list.GetModels() + + // check empty list + if len(list.GetModels()) == 0 { + HandleSuccessWithListData(c, nil, 0) + return + } + + // project ids + var ids []primitive.ObjectID + + // count cache + cache := map[primitive.ObjectID]int{} + + // iterate + for _, d := range data { + p, ok := d.(*models.Project) + if !ok { + HandleErrorInternalServerError(c, errors.ErrorControllerInvalidType) + return + } + ids = append(ids, p.Id) + cache[p.Id] = 0 + } + + // spiders + modelSvc, err := service.NewService() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + spiders, err := modelSvc.GetSpiderList(bson.M{ + "project_id": bson.M{ + "$in": ids, + }, + }, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + for _, s := range spiders { + _, ok := cache[s.ProjectId] + if !ok { + HandleErrorInternalServerError(c, errors.ErrorControllerMissingInCache) + return + } + cache[s.ProjectId]++ + } + + // assign + var projects []models.Project + for _, d := range data { + p := d.(*models.Project) + p.Spiders = cache[p.Id] + projects = append(projects, *p) + } + + HandleSuccessWithListData(c, projects, total) +} + +func newProjectController() *projectController { + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListControllerDelegate(ControllerIdProject, modelSvc.GetBaseService(interfaces.ModelIdProject)) + + return &projectController{ + ListControllerDelegate: *ctr, + } +} diff --git a/core/controllers/result.go b/core/controllers/result.go new file mode 100644 index 00000000..346145fd --- /dev/null +++ b/core/controllers/result.go @@ -0,0 +1,150 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/result" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "net/http" +) + +var ResultController ActionController + +func getResultActions() []Action { + var resultCtx = newResultContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/:id", + HandlerFunc: resultCtx.getList, + }, + } +} + +type resultContext struct { + modelSvc service.ModelService +} + +func (ctx *resultContext) getList(c *gin.Context) { + // data collection id + dcId, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // data source id + var dsId primitive.ObjectID + dsIdStr := c.Query("data_source_id") + if dsIdStr != "" { + dsId, err = primitive.ObjectIDFromHex(dsIdStr) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + } + + // data collection + dc, err := ctx.modelSvc.GetDataCollectionById(dcId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data source + ds, err := ctx.modelSvc.GetDataSourceById(dsId) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + ds = &models.DataSource{} + } else { + HandleErrorInternalServerError(c, err) + return + } + } + + // spider + sq := bson.M{ + "col_id": dc.Id, + "data_source_id": ds.Id, + } + s, err := ctx.modelSvc.GetSpider(sq, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // service + svc, err := result.GetResultService(s.Id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // params + pagination := MustGetPagination(c) + query := ctx.getListQuery(c) + + // get results + data, err := svc.List(query, &generic.ListOptions{ + Sort: []generic.ListSort{{"_id", generic.SortDirectionDesc}}, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + HandleSuccessWithListData(c, nil, 0) + return + } + HandleErrorInternalServerError(c, err) + return + } + + // validate results + if len(data) == 0 { + HandleSuccessWithListData(c, nil, 0) + return + } + + // total count + total, err := svc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // response + HandleSuccessWithListData(c, data, total) +} + +func (ctx *resultContext) getListQuery(c *gin.Context) (q generic.ListQuery) { + f, err := GetFilter(c) + if err != nil { + return q + } + for _, cond := range f.Conditions { + q = append(q, generic.ListQueryCondition{ + Key: cond.Key, + Op: cond.Op, + Value: utils.NormalizeObjectId(cond.Value), + }) + } + return q +} + +func newResultContext() *resultContext { + // context + ctx := &resultContext{} + + var err error + ctx.modelSvc, err = service.NewService() + if err != nil { + panic(err) + } + + return ctx +} diff --git a/core/controllers/result_v2.go b/core/controllers/result_v2.go new file mode 100644 index 00000000..edbeedca --- /dev/null +++ b/core/controllers/result_v2.go @@ -0,0 +1,119 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/result" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" +) + +func GetResultList(c *gin.Context) { + // data collection id + dcId, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // data source id + var dsId primitive.ObjectID + dsIdStr := c.Query("data_source_id") + if dsIdStr != "" { + dsId, err = primitive.ObjectIDFromHex(dsIdStr) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + } + + // data collection + dc, err := service.NewModelServiceV2[models.DataCollectionV2]().GetById(dcId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data source + ds, err := service.NewModelServiceV2[models.DataSourceV2]().GetById(dsId) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + ds = &models.DataSourceV2{} + } else { + HandleErrorInternalServerError(c, err) + return + } + } + + // spider + sq := bson.M{ + "col_id": dc.Id, + "data_source_id": ds.Id, + } + s, err := service.NewModelServiceV2[models.SpiderV2]().GetOne(sq, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // service + svc, err := result.GetResultService(s.Id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // params + pagination := MustGetPagination(c) + query := getResultListQuery(c) + + // get results + data, err := svc.List(query, &generic.ListOptions{ + Sort: []generic.ListSort{{"_id", generic.SortDirectionDesc}}, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + HandleSuccessWithListData(c, nil, 0) + return + } + HandleErrorInternalServerError(c, err) + return + } + + // validate results + if len(data) == 0 { + HandleSuccessWithListData(c, nil, 0) + return + } + + // total count + total, err := svc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // response + HandleSuccessWithListData(c, data, total) +} + +func getResultListQuery(c *gin.Context) (q generic.ListQuery) { + f, err := GetFilter(c) + if err != nil { + return q + } + for _, cond := range f.Conditions { + q = append(q, generic.ListQueryCondition{ + Key: cond.Key, + Op: cond.Op, + Value: utils.NormalizeObjectId(cond.Value), + }) + } + return q +} diff --git a/core/controllers/role.go b/core/controllers/role.go new file mode 100644 index 00000000..d4a286e5 --- /dev/null +++ b/core/controllers/role.go @@ -0,0 +1,3 @@ +package controllers + +var RoleController ListController diff --git a/core/controllers/router_v2.go b/core/controllers/router_v2.go new file mode 100644 index 00000000..91e42ae8 --- /dev/null +++ b/core/controllers/router_v2.go @@ -0,0 +1,387 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/middlewares" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/gin-gonic/gin" + "net/http" +) + +type RouterGroups struct { + AuthGroup *gin.RouterGroup + AnonymousGroup *gin.RouterGroup +} + +func NewRouterGroups(app *gin.Engine) (groups *RouterGroups) { + return &RouterGroups{ + AuthGroup: app.Group("/", middlewares.AuthorizationMiddlewareV2()), + AnonymousGroup: app.Group("/"), + } +} + +func RegisterController[T any](group *gin.RouterGroup, basePath string, ctr *BaseControllerV2[T]) { + actionPaths := make(map[string]bool) + for _, action := range ctr.actions { + group.Handle(action.Method, basePath+action.Path, action.HandlerFunc) + path := basePath + action.Path + key := action.Method + " - " + path + actionPaths[key] = true + } + registerBuiltinHandler(group, http.MethodGet, basePath+"", ctr.GetList, actionPaths) + registerBuiltinHandler(group, http.MethodGet, basePath+"/:id", ctr.GetById, actionPaths) + registerBuiltinHandler(group, http.MethodPost, basePath+"", ctr.Post, actionPaths) + registerBuiltinHandler(group, http.MethodPut, basePath+"/:id", ctr.PutById, actionPaths) + registerBuiltinHandler(group, http.MethodPatch, basePath+"", ctr.PatchList, actionPaths) + registerBuiltinHandler(group, http.MethodDelete, basePath+"/:id", ctr.DeleteById, actionPaths) + registerBuiltinHandler(group, http.MethodDelete, basePath+"", ctr.DeleteList, actionPaths) +} + +func RegisterActions(group *gin.RouterGroup, basePath string, actions []Action) { + for _, action := range actions { + group.Handle(action.Method, basePath+action.Path, action.HandlerFunc) + } +} + +func registerBuiltinHandler(group *gin.RouterGroup, method, path string, handlerFunc gin.HandlerFunc, existingActionPaths map[string]bool) { + key := method + " - " + path + _, ok := existingActionPaths[key] + if ok { + return + } + group.Handle(method, path, handlerFunc) +} + +func InitRoutes(app *gin.Engine) (err error) { + // routes groups + groups := NewRouterGroups(app) + + RegisterController(groups.AuthGroup, "/data/collections", NewControllerV2[models.DataCollectionV2]()) + RegisterController(groups.AuthGroup, "/data-sources", NewControllerV2[models.DataSourceV2]()) + RegisterController(groups.AuthGroup, "/environments", NewControllerV2[models.EnvironmentV2]()) + RegisterController(groups.AuthGroup, "/gits", NewControllerV2[models.GitV2]()) + RegisterController(groups.AuthGroup, "/nodes", NewControllerV2[models.NodeV2]()) + RegisterController(groups.AuthGroup, "/notifications/settings", NewControllerV2[models.SettingV2]()) + RegisterController(groups.AuthGroup, "/permissions", NewControllerV2[models.PermissionV2]()) + RegisterController(groups.AuthGroup, "/projects", NewControllerV2[models.ProjectV2]()) + RegisterController(groups.AuthGroup, "/roles", NewControllerV2[models.RoleV2]()) + RegisterController(groups.AuthGroup, "/schedules", NewControllerV2[models.ScheduleV2]( + Action{ + Method: http.MethodPost, + Path: "", + HandlerFunc: PostSchedule, + }, + Action{ + Method: http.MethodPut, + Path: "/:id", + HandlerFunc: PutScheduleById, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/enable", + HandlerFunc: PostScheduleEnable, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/disable", + HandlerFunc: PostScheduleDisable, + }, + )) + RegisterController(groups.AuthGroup, "/spiders", NewControllerV2[models.SpiderV2]( + Action{ + Method: http.MethodGet, + Path: "/:id", + HandlerFunc: GetSpiderById, + }, + Action{ + Method: http.MethodGet, + Path: "", + HandlerFunc: GetSpiderList, + }, + Action{ + Method: http.MethodPost, + Path: "", + HandlerFunc: PostSpider, + }, + Action{ + Method: http.MethodPut, + Path: "/:id", + HandlerFunc: PutSpiderById, + }, + Action{ + Method: http.MethodDelete, + Path: "/:id", + HandlerFunc: DeleteSpiderById, + }, + Action{ + Method: http.MethodDelete, + Path: "", + HandlerFunc: DeleteSpiderList, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/files/list", + HandlerFunc: GetSpiderListDir, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/files/get", + HandlerFunc: GetSpiderFile, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/files/info", + HandlerFunc: GetSpiderFileInfo, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/files/save", + HandlerFunc: PostSpiderSaveFile, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/files/save/batch", + HandlerFunc: PostSpiderSaveFiles, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/files/save/dir", + HandlerFunc: PostSpiderSaveDir, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/files/rename", + HandlerFunc: PostSpiderRenameFile, + }, + Action{ + Method: http.MethodDelete, + Path: "/:id/files", + HandlerFunc: DeleteSpiderFile, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/files/copy", + HandlerFunc: PostSpiderCopyFile, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/files/export", + HandlerFunc: PostSpiderExport, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/run", + HandlerFunc: PostSpiderRun, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/git", + HandlerFunc: GetSpiderGit, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/git/remote-refs", + HandlerFunc: GetSpiderGitRemoteRefs, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/git/checkout", + HandlerFunc: PostSpiderGitCheckout, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/git/pull", + HandlerFunc: PostSpiderGitPull, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/git/commit", + HandlerFunc: PostSpiderGitCommit, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/data-source", + HandlerFunc: GetSpiderDataSource, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/data-source/:ds_id", + HandlerFunc: PostSpiderDataSource, + }, + )) + RegisterController(groups.AuthGroup, "/tasks", NewControllerV2[models.TaskV2]( + Action{ + Method: http.MethodGet, + Path: "/:id", + HandlerFunc: GetTaskById, + }, + Action{ + Method: http.MethodGet, + Path: "", + HandlerFunc: GetTaskList, + }, + Action{ + Method: http.MethodDelete, + Path: "/:id", + HandlerFunc: DeleteTaskById, + }, + Action{ + Method: http.MethodDelete, + Path: "", + HandlerFunc: DeleteList, + }, + Action{ + Method: http.MethodPost, + Path: "/run", + HandlerFunc: PostTaskRun, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/restart", + HandlerFunc: PostTaskRestart, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/cancel", + HandlerFunc: PostTaskCancel, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/logs", + HandlerFunc: GetTaskLogs, + }, + Action{ + Method: http.MethodGet, + Path: "/:id/data", + HandlerFunc: GetTaskData, + }, + )) + RegisterController(groups.AuthGroup, "/tokens", NewControllerV2[models.TokenV2]( + Action{ + Method: http.MethodPost, + Path: "", + HandlerFunc: PostToken, + }, + )) + RegisterController(groups.AuthGroup, "/users", NewControllerV2[models.UserV2]( + Action{ + Method: http.MethodPost, + Path: "", + HandlerFunc: PostUser, + }, + Action{ + Method: http.MethodPost, + Path: "/:id/change-password", + HandlerFunc: PostUserChangePassword, + }, + Action{ + Method: http.MethodGet, + Path: "/me", + HandlerFunc: GetUserMe, + }, + Action{ + Method: http.MethodPut, + Path: "/me", + HandlerFunc: PutUserById, + }, + )) + + RegisterActions(groups.AuthGroup, "/results", []Action{ + { + Method: http.MethodGet, + Path: "/:id", + HandlerFunc: GetResultList, + }, + }) + RegisterActions(groups.AuthGroup, "/export", []Action{ + { + Method: http.MethodPost, + Path: "/:type", + HandlerFunc: PostExport, + }, + { + Method: http.MethodGet, + Path: "/:type/:id", + HandlerFunc: GetExport, + }, + { + Method: http.MethodGet, + Path: "/:type/:id/download", + HandlerFunc: GetExportDownload, + }, + }) + RegisterActions(groups.AuthGroup, "/filters", []Action{ + { + Method: http.MethodGet, + Path: "/:col", + HandlerFunc: GetFilterColFieldOptions, + }, + { + Method: http.MethodGet, + Path: "/:col/:value", + HandlerFunc: GetFilterColFieldOptions, + }, + { + Method: http.MethodGet, + Path: "/:col/:value/:label", + HandlerFunc: GetFilterColFieldOptions, + }, + }) + RegisterActions(groups.AuthGroup, "/settings", []Action{ + { + Method: http.MethodGet, + Path: "/:id", + HandlerFunc: GetSetting, + }, + { + Method: http.MethodPut, + Path: "/:id", + HandlerFunc: PutSetting, + }, + }) + RegisterActions(groups.AuthGroup, "/stats", []Action{ + { + Method: http.MethodGet, + Path: "/overview", + HandlerFunc: GetStatsOverview, + }, + { + Method: http.MethodGet, + Path: "/daily", + HandlerFunc: GetStatsDaily, + }, + { + Method: http.MethodGet, + Path: "/tasks", + HandlerFunc: GetStatsTasks, + }, + }) + + RegisterActions(groups.AnonymousGroup, "/system-info", []Action{ + { + Path: "", + Method: http.MethodGet, + HandlerFunc: GetSystemInfo, + }, + }) + RegisterActions(groups.AnonymousGroup, "/version", []Action{ + { + Method: http.MethodGet, + Path: "", + HandlerFunc: GetVersion, + }, + }) + RegisterActions(groups.AnonymousGroup, "/", []Action{ + { + Method: http.MethodPost, + Path: "/login", + HandlerFunc: PostLogin, + }, + { + Method: http.MethodPost, + Path: "/logout", + HandlerFunc: PostLogout, + }, + }) + + return nil +} diff --git a/core/controllers/router_v2_test.go b/core/controllers/router_v2_test.go new file mode 100644 index 00000000..47642324 --- /dev/null +++ b/core/controllers/router_v2_test.go @@ -0,0 +1,91 @@ +package controllers_test + +import ( + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestRouterGroups(t *testing.T) { + router := gin.Default() + groups := controllers.NewRouterGroups(router) + + assertions := []struct { + group *gin.RouterGroup + name string + }{ + {groups.AuthGroup, "AuthGroup"}, + {groups.AnonymousGroup, "AnonymousGroup"}, + } + + for _, a := range assertions { + assert.NotNil(t, a.group, a.name+" should not be nil") + } +} + +func TestRegisterController_Routes(t *testing.T) { + router := gin.Default() + groups := controllers.NewRouterGroups(router) + ctr := controllers.NewControllerV2[models.TestModel]() + basePath := "/testmodels" + + controllers.RegisterController(groups.AuthGroup, basePath, ctr) + + // Check if all routes are registered + routes := router.Routes() + + var methodPaths []string + for _, route := range routes { + methodPaths = append(methodPaths, route.Method+" - "+route.Path) + } + + expectedRoutes := []gin.RouteInfo{ + {Method: "GET", Path: basePath}, + {Method: "GET", Path: basePath + "/:id"}, + {Method: "POST", Path: basePath}, + {Method: "PUT", Path: basePath + "/:id"}, + {Method: "PATCH", Path: basePath}, + {Method: "DELETE", Path: basePath + "/:id"}, + {Method: "DELETE", Path: basePath}, + } + + assert.Equal(t, len(expectedRoutes), len(routes)) + for _, route := range expectedRoutes { + assert.Contains(t, methodPaths, route.Method+" - "+route.Path) + } +} + +func TestInitRoutes_ProjectsRoute(t *testing.T) { + router := gin.Default() + + controllers.InitRoutes(router) + + // Check if the projects route is registered + routes := router.Routes() + + var methodPaths []string + for _, route := range routes { + methodPaths = append(methodPaths, route.Method+" - "+route.Path) + } + + expectedRoutes := []gin.RouteInfo{ + {Method: "GET", Path: "/projects"}, + {Method: "GET", Path: "/projects/:id"}, + {Method: "POST", Path: "/projects"}, + {Method: "PUT", Path: "/projects/:id"}, + {Method: "PATCH", Path: "/projects"}, + {Method: "DELETE", Path: "/projects/:id"}, + {Method: "DELETE", Path: "/projects"}, + } + + for _, route := range expectedRoutes { + assert.Contains(t, methodPaths, route.Method+" - "+route.Path) + } +} + +func TestMain(m *testing.M) { + gin.SetMode(gin.TestMode) + m.Run() +} diff --git a/core/controllers/schedule.go b/core/controllers/schedule.go new file mode 100644 index 00000000..71ba2b67 --- /dev/null +++ b/core/controllers/schedule.go @@ -0,0 +1,221 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" +) + +var ScheduleController *scheduleController + +func getScheduleActions() []Action { + scheduleCtx := newScheduleContext() + return []Action{ + { + Method: http.MethodPost, + Path: "/:id/enable", + HandlerFunc: scheduleCtx.enable, + }, + { + Method: http.MethodPost, + Path: "/:id/disable", + HandlerFunc: scheduleCtx.disable, + }, + } +} + +type scheduleController struct { + ListActionControllerDelegate + d ListActionControllerDelegate + ctx *scheduleContext +} + +func (ctr *scheduleController) Post(c *gin.Context) { + var s models.Schedule + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := delegate.NewModelDelegate(&s, GetUserFromContext(c)).Add(); err != nil { + HandleErrorInternalServerError(c, err) + return + } + if s.Enabled { + if err := ctr.ctx.scheduleSvc.Enable(&s, GetUserFromContext(c)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + HandleSuccessWithData(c, s) +} + +func (ctr *scheduleController) Put(c *gin.Context) { + id := c.Param("id") + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + var s models.Schedule + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + if s.GetId() != oid { + HandleErrorBadRequest(c, errors.ErrorHttpBadRequest) + return + } + if err := delegate.NewModelDelegate(&s).Save(); err != nil { + HandleErrorInternalServerError(c, err) + return + } + if s.Enabled { + if err := ctr.ctx.scheduleSvc.Disable(&s, GetUserFromContext(c)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := ctr.ctx.scheduleSvc.Enable(&s, GetUserFromContext(c)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + HandleSuccessWithData(c, s) +} + +func (ctr *scheduleController) Delete(c *gin.Context) { + id := c.Param("id") + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + s, err := ctr.ctx.modelSvc.GetScheduleById(oid) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := ctr.ctx.scheduleSvc.Disable(s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := delegate.NewModelDelegate(s, GetUserFromContext(c)).Delete(); err != nil { + HandleErrorInternalServerError(c, err) + return + } +} + +func (ctr *scheduleController) DeleteList(c *gin.Context) { + payload, err := NewJsonBinder(interfaces.ModelIdSchedule).BindBatchRequestPayload(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + for _, id := range payload.Ids { + s, err := ctr.ctx.modelSvc.GetScheduleById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := ctr.ctx.scheduleSvc.Disable(s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + if err := ctr.ctx.modelSvc.GetBaseService(interfaces.ModelIdSchedule).DeleteList(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *scheduleContext) enable(c *gin.Context) { + s, err := ctx._getSchedule(c) + if err != nil { + return + } + if err := ctx.scheduleSvc.Enable(s, GetUserFromContext(c)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *scheduleContext) disable(c *gin.Context) { + s, err := ctx._getSchedule(c) + if err != nil { + return + } + if err := ctx.scheduleSvc.Disable(s, GetUserFromContext(c)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *scheduleContext) _getSchedule(c *gin.Context) (s *models.Schedule, err error) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + s, err = ctx.modelSvc.GetScheduleById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + return s, nil +} + +type scheduleContext struct { + modelSvc service.ModelService + scheduleSvc interfaces.ScheduleService +} + +func newScheduleContext() *scheduleContext { + // context + ctx := &scheduleContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + scheduleSvc interfaces.ScheduleService, + ) { + ctx.modelSvc = modelSvc + ctx.scheduleSvc = scheduleSvc + }); err != nil { + panic(err) + } + + return ctx +} + +func newScheduleController() *scheduleController { + actions := getScheduleActions() + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListPostActionControllerDelegate(ControllerIdSchedule, modelSvc.GetBaseService(interfaces.ModelIdSchedule), actions) + d := NewListPostActionControllerDelegate(ControllerIdSchedule, modelSvc.GetBaseService(interfaces.ModelIdSchedule), actions) + ctx := newScheduleContext() + + return &scheduleController{ + ListActionControllerDelegate: *ctr, + d: *d, + ctx: ctx, + } +} diff --git a/core/controllers/schedule_v2.go b/core/controllers/schedule_v2.go new file mode 100644 index 00000000..3e19da76 --- /dev/null +++ b/core/controllers/schedule_v2.go @@ -0,0 +1,130 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/schedule" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func PostSchedule(c *gin.Context) { + var s models.ScheduleV2 + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + + u := GetUserFromContextV2(c) + + modelSvc := service.NewModelServiceV2[models.ScheduleV2]() + + s.SetCreated(u.Id) + s.SetUpdated(u.Id) + id, err := modelSvc.InsertOne(s) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + s.Id = id + + if s.Enabled { + scheduleSvc, err := schedule.GetScheduleServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := scheduleSvc.Enable(s, u.Id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + HandleSuccessWithData(c, s) +} + +func PutScheduleById(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + var s models.ScheduleV2 + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + if s.Id != id { + HandleErrorBadRequest(c, errors.ErrorHttpBadRequest) + return + } + + modelSvc := service.NewModelServiceV2[models.ScheduleV2]() + err = modelSvc.ReplaceById(id, s) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + scheduleSvc, err := schedule.GetScheduleServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + u := GetUserFromContextV2(c) + + if s.Enabled { + if err := scheduleSvc.Enable(s, u.Id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } else { + if err := scheduleSvc.Disable(s, u.Id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + HandleSuccessWithData(c, s) +} + +func PostScheduleEnable(c *gin.Context) { + postScheduleEnableDisableFunc(true)(c) +} + +func PostScheduleDisable(c *gin.Context) { + postScheduleEnableDisableFunc(false)(c) +} + +func postScheduleEnableDisableFunc(isEnable bool) func(c *gin.Context) { + return func(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + svc, err := schedule.GetScheduleServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + s, err := service.NewModelServiceV2[models.ScheduleV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + u := GetUserFromContextV2(c) + if isEnable { + err = svc.Enable(*s, u.Id) + } else { + err = svc.Disable(*s, u.Id) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) + } +} diff --git a/core/controllers/setting.go b/core/controllers/setting.go new file mode 100644 index 00000000..068e2379 --- /dev/null +++ b/core/controllers/setting.go @@ -0,0 +1,84 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" +) + +var SettingController *settingController + +type settingController struct { + ListControllerDelegate +} + +func (ctr *settingController) Get(c *gin.Context) { + // key + key := c.Param("id") + + // model service + modelSvc, err := service.NewService() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // setting + s, err := modelSvc.GetSettingByKey(key, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, s) +} + +func (ctr *settingController) Put(c *gin.Context) { + // key + key := c.Param("id") + + // settings + var s models.Setting + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // model service + modelSvc, err := service.NewService() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // setting + _s, err := modelSvc.GetSettingByKey(key, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // save + _s.Value = s.Value + if err := delegate.NewModelDelegate(_s).Save(); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func newSettingController() *settingController { + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListControllerDelegate(ControllerIdSetting, modelSvc.GetBaseService(interfaces.ModelIdSetting)) + + return &settingController{ + ListControllerDelegate: *ctr, + } +} diff --git a/core/controllers/setting_v2.go b/core/controllers/setting_v2.go new file mode 100644 index 00000000..326873b9 --- /dev/null +++ b/core/controllers/setting_v2.go @@ -0,0 +1,56 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" +) + +func GetSetting(c *gin.Context) { + // key + key := c.Param("id") + + // setting + s, err := service.NewModelServiceV2[models.SettingV2]().GetOne(bson.M{"key": key}, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, s) +} + +func PutSetting(c *gin.Context) { + // key + key := c.Param("id") + + // settings + var s models.Setting + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + modelSvc := service.NewModelServiceV2[models.SettingV2]() + + // setting + _s, err := modelSvc.GetOne(bson.M{"key": key}, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + u := GetUserFromContextV2(c) + + // save + _s.Value = s.Value + _s.SetUpdated(u.Id) + err = modelSvc.ReplaceOne(bson.M{"key": key}, *_s) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} diff --git a/core/controllers/spider.go b/core/controllers/spider.go new file mode 100644 index 00000000..e76ba4d2 --- /dev/null +++ b/core/controllers/spider.go @@ -0,0 +1,1333 @@ +package controllers + +import ( + "bytes" + "fmt" + "github.com/crawlab-team/crawlab-db/mongo" + vcs "github.com/crawlab-team/crawlab-vcs" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/errors" + fs2 "github.com/crawlab-team/crawlab/core/fs" + "github.com/crawlab-team/crawlab/core/interfaces" + delegate2 "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "io" + "math" + "net/http" + "os" + "path/filepath" + "strings" +) + +var SpiderController *spiderController + +func getSpiderActions() []Action { + ctx := newSpiderContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/:id/files/list", + HandlerFunc: ctx.listDir, + }, + { + Method: http.MethodGet, + Path: "/:id/files/get", + HandlerFunc: ctx.getFile, + }, + { + Method: http.MethodGet, + Path: "/:id/files/info", + HandlerFunc: ctx.getFileInfo, + }, + { + Method: http.MethodPost, + Path: "/:id/files/save", + HandlerFunc: ctx.saveFile, + }, + { + Method: http.MethodPost, + Path: "/:id/files/save/dir", + HandlerFunc: ctx.saveDir, + }, + { + Method: http.MethodPost, + Path: "/:id/files/rename", + HandlerFunc: ctx.renameFile, + }, + { + Method: http.MethodPost, + Path: "/:id/files/delete", + HandlerFunc: ctx.deleteFile, + }, + { + Method: http.MethodPost, + Path: "/:id/files/copy", + HandlerFunc: ctx.copyFile, + }, + { + Path: "/:id/files/export", + Method: http.MethodPost, + HandlerFunc: ctx.postExport, + }, + { + Method: http.MethodPost, + Path: "/:id/run", + HandlerFunc: ctx.run, + }, + { + Method: http.MethodGet, + Path: "/:id/git", + HandlerFunc: ctx.getGit, + }, + { + Method: http.MethodGet, + Path: "/:id/git/remote-refs", + HandlerFunc: ctx.getGitRemoteRefs, + }, + { + Method: http.MethodPost, + Path: "/:id/git/checkout", + HandlerFunc: ctx.gitCheckout, + }, + { + Method: http.MethodPost, + Path: "/:id/git/pull", + HandlerFunc: ctx.gitPull, + }, + { + Method: http.MethodPost, + Path: "/:id/git/commit", + HandlerFunc: ctx.gitCommit, + }, + //{ + // Method: http.MethodPost, + // Path: "/:id/clone", + // HandlerFunc: ctx.clone, + //}, + { + Path: "/:id/data-source", + Method: http.MethodGet, + HandlerFunc: ctx.getDataSource, + }, + { + Path: "/:id/data-source/:ds_id", + Method: http.MethodPost, + HandlerFunc: ctx.postDataSource, + }, + } +} + +type spiderController struct { + ListActionControllerDelegate + d ListActionControllerDelegate + ctx *spiderContext +} + +func (ctr *spiderController) Get(c *gin.Context) { + ctr.ctx._get(c) +} + +func (ctr *spiderController) Post(c *gin.Context) { + s, err := ctr.ctx._post(c) + if err != nil { + return + } + HandleSuccessWithData(c, s) +} + +func (ctr *spiderController) Put(c *gin.Context) { + s, err := ctr.ctx._put(c) + if err != nil { + return + } + HandleSuccessWithData(c, s) +} + +func (ctr *spiderController) Delete(c *gin.Context) { + if err := ctr.ctx._delete(c); err != nil { + return + } + HandleSuccess(c) +} + +func (ctr *spiderController) GetList(c *gin.Context) { + withStats := c.Query("stats") + if withStats == "" { + ctr.d.GetList(c) + return + } + ctr.ctx._getListWithStats(c) +} + +func (ctr *spiderController) DeleteList(c *gin.Context) { + if err := ctr.ctx._deleteList(c); err != nil { + return + } + HandleSuccess(c) +} + +type spiderContext struct { + modelSvc service.ModelService + modelSpiderSvc interfaces.ModelBaseService + modelSpiderStatSvc interfaces.ModelBaseService + modelTaskSvc interfaces.ModelBaseService + modelTaskStatSvc interfaces.ModelBaseService + adminSvc interfaces.SpiderAdminService +} + +func (ctx *spiderContext) listDir(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodGet) + if err != nil { + return + } + + files, err := fsSvc.List(payload.Path) + if err != nil { + if err.Error() != "response status code: 404" { + HandleErrorInternalServerError(c, err) + return + } + } + + HandleSuccessWithData(c, files) +} + +func (ctx *spiderContext) getFile(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodGet) + if err != nil { + return + } + + data, err := fsSvc.GetFile(payload.Path) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + data = utils.TrimFileData(data) + + HandleSuccessWithData(c, string(data)) +} + +func (ctx *spiderContext) getFileInfo(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodGet) + if err != nil { + return + } + + info, err := fsSvc.GetFileInfo(payload.Path) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, info) +} + +func (ctx *spiderContext) saveFile(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodPost) + if err != nil { + return + } + + if err := fsSvc.Save(payload.Path, []byte(payload.Data)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) saveDir(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodPost) + if err != nil { + return + } + + if err := fsSvc.CreateDir(payload.Path); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) renameFile(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodPost) + if err != nil { + return + } + + if err := fsSvc.Rename(payload.Path, payload.NewPath); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) deleteFile(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodPost) + if err != nil { + return + } + + if err := fsSvc.Delete(payload.Path); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) copyFile(c *gin.Context) { + _, payload, fsSvc, err := ctx._processFileRequest(c, http.MethodPost) + if err != nil { + return + } + + if err := fsSvc.Copy(payload.Path, payload.NewPath); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) run(c *gin.Context) { + // spider id + id, err := ctx._processActionRequest(c) + if err != nil { + return + } + + // options + var opts interfaces.SpiderRunOptions + if err := c.ShouldBindJSON(&opts); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // user + if u := GetUserFromContext(c); u != nil { + opts.UserId = u.GetId() + } + + // schedule + taskIds, err := ctx.adminSvc.Schedule(id, &opts) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, taskIds) +} + +func (ctx *spiderContext) getGit(c *gin.Context) { + // spider id + id, err := ctx._processActionRequest(c) + if err != nil { + return + } + + // git client + gitClient, err := ctx._getGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // current branch + currentBranch, err := gitClient.GetCurrentBranch() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // branches + branches, err := gitClient.GetBranches() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if branches == nil || len(branches) == 0 && currentBranch != "" { + branches = []vcs.GitRef{{Name: currentBranch}} + } + + // changes + changes, err := gitClient.GetStatus() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // logs + logs, err := gitClient.GetLogsWithRefs() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // ignore + ignore, err := ctx._getGitIgnore(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // git + _git, err := ctx.modelSvc.GetGitById(id) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + HandleErrorInternalServerError(c, err) + return + } + } + + // response + res := bson.M{ + "current_branch": currentBranch, + "branches": branches, + "changes": changes, + "logs": logs, + "ignore": ignore, + "git": _git, + } + + HandleSuccessWithData(c, res) +} + +func (ctx *spiderContext) getGitRemoteRefs(c *gin.Context) { + // spider id + id, err := ctx._processActionRequest(c) + if err != nil { + return + } + + // remote name + remoteName := c.Query("remote") + if remoteName == "" { + remoteName = vcs.GitRemoteNameOrigin + } + + // git client + gitClient, err := ctx._getGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // refs + refs, err := gitClient.GetRemoteRefs(remoteName) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, refs) +} + +func (ctx *spiderContext) gitCheckout(c *gin.Context) { + // payload + var payload entity.GitPayload + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider id + id, err := ctx._processActionRequest(c) + if err != nil { + return + } + + // git client + gitClient, err := ctx._getGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // branch to pull + var branch string + if payload.Branch == "" { + // by default current branch + branch, err = gitClient.GetCurrentBranch() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + } else { + // payload branch + branch = payload.Branch + } + + // checkout + if err := ctx._gitCheckout(gitClient, constants.GitRemoteNameOrigin, branch); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) gitPull(c *gin.Context) { + // payload + var payload entity.GitPayload + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider id + id, err := ctx._processActionRequest(c) + if err != nil { + return + } + + // git + g, err := ctx.modelSvc.GetGitById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // attempt to sync git + _ = ctx.adminSvc.SyncGitOne(g) + + HandleSuccess(c) +} + +func (ctx *spiderContext) gitCommit(c *gin.Context) { + // payload + var payload entity.GitPayload + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider id + id, err := ctx._processActionRequest(c) + if err != nil { + return + } + + // git client + gitClient, err := ctx._getGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // add + for _, p := range payload.Paths { + if err := gitClient.Add(p); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + // commit + if err := gitClient.Commit(payload.CommitMessage); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // push + if err := gitClient.Push( + vcs.WithRemoteNamePush(vcs.GitRemoteNameOrigin), + ); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) getDataSource(c *gin.Context) { + // spider id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider + s, err := ctx.modelSvc.GetSpiderById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data source + ds, err := ctx.modelSvc.GetDataSourceById(s.DataSourceId) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + HandleSuccess(c) + return + } + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, ds) +} + +func (ctx *spiderContext) postDataSource(c *gin.Context) { + // spider id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // data source id + dsId, err := primitive.ObjectIDFromHex(c.Param("ds_id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider + s, err := ctx.modelSvc.GetSpiderById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data source + if !dsId.IsZero() { + _, err = ctx.modelSvc.GetDataSourceById(dsId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + // save data source id + s.DataSourceId = dsId + if err := delegate2.NewModelDelegate(s).Save(); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *spiderContext) postExport(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // zip file path + zipFilePath, err := ctx.adminSvc.Export(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // download + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", zipFilePath)) + c.File(zipFilePath) +} + +func (ctx *spiderContext) _get(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + s, err := ctx.modelSvc.GetSpiderById(id) + if err == mongo2.ErrNoDocuments { + HandleErrorNotFound(c, err) + return + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // stat + s.Stat, err = ctx.modelSvc.GetSpiderStatById(s.GetId()) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data collection + if !s.ColId.IsZero() { + col, err := ctx.modelSvc.GetDataCollectionById(s.ColId) + if err != nil { + if err != mongo2.ErrNoDocuments { + HandleErrorInternalServerError(c, err) + return + } + } else { + s.ColName = col.Name + } + } + + HandleSuccessWithData(c, s) +} + +func (ctx *spiderContext) _post(c *gin.Context) (s *models.Spider, err error) { + // bind + s = &models.Spider{} + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return nil, err + } + + // upsert data collection + if err := ctx._upsertDataCollection(c, s); err != nil { + HandleErrorInternalServerError(c, err) + return nil, err + } + + // add + if err := delegate2.NewModelDelegate(s, GetUserFromContext(c)).Add(); err != nil { + HandleErrorInternalServerError(c, err) + return nil, err + } + + // add stat + st := &models.SpiderStat{ + Id: s.GetId(), + } + if err := delegate2.NewModelDelegate(st, GetUserFromContext(c)).Add(); err != nil { + HandleErrorInternalServerError(c, err) + return nil, err + } + + return s, nil +} + +func (ctx *spiderContext) _put(c *gin.Context) (s *models.Spider, err error) { + // bind + s = &models.Spider{} + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return nil, err + } + + // upsert data collection + if err := ctx._upsertDataCollection(c, s); err != nil { + HandleErrorInternalServerError(c, err) + return nil, err + } + + // save + if err := delegate2.NewModelDelegate(s, GetUserFromContext(c)).Save(); err != nil { + HandleErrorInternalServerError(c, err) + return nil, err + } + + return s, nil +} + +func (ctx *spiderContext) _delete(c *gin.Context) (err error) { + id := c.Param("id") + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) (err error) { + // delete spider + s, err := ctx.modelSvc.GetSpiderById(oid) + if err != nil { + return err + } + if err := delegate2.NewModelDelegate(s, GetUserFromContext(c)).Delete(); err != nil { + return err + } + + // delete spider stat + ss, err := ctx.modelSvc.GetSpiderStatById(oid) + if err != nil { + return err + } + if err := delegate2.NewModelDelegate(ss, GetUserFromContext(c)).Delete(); err != nil { + return err + } + + // related tasks + tasks, err := ctx.modelSvc.GetTaskList(bson.M{"spider_id": oid}, nil) + if err != nil { + return err + } + + // task ids + var taskIds []primitive.ObjectID + for _, t := range tasks { + taskIds = append(taskIds, t.Id) + } + + // delete related tasks + if err := ctx.modelTaskSvc.DeleteList(bson.M{"_id": bson.M{"$in": taskIds}}); err != nil { + return err + } + + // delete related task stats + if err := ctx.modelTaskStatSvc.DeleteList(bson.M{"_id": bson.M{"$in": taskIds}}); err != nil { + return err + } + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return err + } + + return nil +} + +func (ctx *spiderContext) _getListWithStats(c *gin.Context) { + // params + pagination := MustGetPagination(c) + query := MustGetFilterQuery(c) + sort := MustGetSortOption(c) + + // get list + l, err := ctx.modelSpiderSvc.GetList(query, &mongo.FindOptions{ + Sort: sort, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + HandleErrorNotFound(c, err) + } else { + HandleErrorInternalServerError(c, err) + } + return + } + + // check empty list + if len(l.GetModels()) == 0 { + HandleSuccessWithListData(c, nil, 0) + return + } + + // ids + var ids []primitive.ObjectID + for _, d := range l.GetModels() { + s := d.(*models.Spider) + ids = append(ids, s.GetId()) + } + + // total count + total, err := ctx.modelSpiderSvc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // stat list + query = bson.M{ + "_id": bson.M{ + "$in": ids, + }, + } + stats, err := ctx.modelSvc.GetSpiderStatList(query, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // cache stat list to dict + dict := map[primitive.ObjectID]models.SpiderStat{} + var tids []primitive.ObjectID + for _, st := range stats { + if st.Tasks > 0 { + taskCount := int64(st.Tasks) + st.AverageWaitDuration = int64(math.Round(float64(st.WaitDuration) / float64(taskCount))) + st.AverageRuntimeDuration = int64(math.Round(float64(st.RuntimeDuration) / float64(taskCount))) + st.AverageTotalDuration = int64(math.Round(float64(st.TotalDuration) / float64(taskCount))) + } + dict[st.GetId()] = st + + if !st.LastTaskId.IsZero() { + tids = append(tids, st.LastTaskId) + } + } + + // task list and stats + var tasks []models.Task + dictTask := map[primitive.ObjectID]models.Task{} + dictTaskStat := map[primitive.ObjectID]models.TaskStat{} + if len(tids) > 0 { + // task list + queryTask := bson.M{ + "_id": bson.M{ + "$in": tids, + }, + } + tasks, err = ctx.modelSvc.GetTaskList(queryTask, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // task stats list + taskStats, err := ctx.modelSvc.GetTaskStatList(queryTask, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // cache task stats to dict + for _, st := range taskStats { + dictTaskStat[st.GetId()] = st + } + + // cache task list to dict + for _, t := range tasks { + st, ok := dictTaskStat[t.GetId()] + if ok { + t.Stat = &st + } + dictTask[t.GetSpiderId()] = t + } + } + + // iterate list again + var data []interface{} + for _, d := range l.GetModels() { + s := d.(*models.Spider) + + // spider stat + st, ok := dict[s.GetId()] + if ok { + s.Stat = &st + + // last task + t, ok := dictTask[s.GetId()] + if ok { + s.Stat.LastTask = &t + } + } + + // add to list + data = append(data, *s) + } + + // response + HandleSuccessWithListData(c, data, total) +} + +func (ctx *spiderContext) _deleteList(c *gin.Context) (err error) { + payload, err := NewJsonBinder(ControllerIdSpider).BindBatchRequestPayload(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) (err error) { + // delete spiders + if err := ctx.modelSpiderSvc.DeleteList(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + // delete spider stats + if err := ctx.modelSpiderStatSvc.DeleteList(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + // related tasks + tasks, err := ctx.modelSvc.GetTaskList(bson.M{"spider_id": bson.M{"$in": payload.Ids}}, nil) + if err != nil { + return err + } + + // task ids + var taskIds []primitive.ObjectID + for _, t := range tasks { + taskIds = append(taskIds, t.Id) + } + + // delete related tasks + if err := ctx.modelTaskSvc.DeleteList(bson.M{"_id": bson.M{"$in": taskIds}}); err != nil { + return err + } + + // delete related task stats + if err := ctx.modelTaskStatSvc.DeleteList(bson.M{"_id": bson.M{"$in": taskIds}}); err != nil { + return err + } + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return err + } + + return nil +} + +func (ctx *spiderContext) _processFileRequest(c *gin.Context, method string) (id primitive.ObjectID, payload entity.FileRequestPayload, fsSvc interfaces.FsServiceV2, err error) { + // id + id, err = primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // payload + contentType := c.GetHeader("Content-Type") + if strings.HasPrefix(contentType, "multipart/form-data") { + // multipart/form-data + payload, err = ctx._getFileRequestMultipartPayload(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + } else { + // query or application/json + switch method { + case http.MethodGet: + err = c.ShouldBindQuery(&payload) + default: + err = c.ShouldBindJSON(&payload) + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + // fs service + workspacePath := viper.GetString("workspace") + fsSvc = fs2.NewFsServiceV2(filepath.Join(workspacePath, id.Hex())) + + return +} + +func (ctx *spiderContext) _getFileRequestMultipartPayload(c *gin.Context) (payload entity.FileRequestPayload, err error) { + fh, err := c.FormFile("file") + if err != nil { + return + } + f, err := fh.Open() + if err != nil { + return + } + buf := bytes.NewBuffer(nil) + if _, err = io.Copy(buf, f); err != nil { + return + } + payload.Path = c.PostForm("path") + payload.Data = buf.String() + return +} + +func (ctx *spiderContext) _processActionRequest(c *gin.Context) (id primitive.ObjectID, err error) { + // id + id, err = primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + return +} + +func (ctx *spiderContext) _upsertDataCollection(c *gin.Context, s *models.Spider) (err error) { + if s.ColId.IsZero() { + // validate + if s.ColName == "" { + return trace.TraceError(errors.ErrorControllerMissingRequestFields) + } + // no id + dc, err := ctx.modelSvc.GetDataCollectionByName(s.ColName, nil) + if err != nil { + if err == mongo2.ErrNoDocuments { + // not exists, add new + dc = &models.DataCollection{Name: s.ColName} + if err := delegate2.NewModelDelegate(dc, GetUserFromContext(c)).Add(); err != nil { + return err + } + } else { + // error + return err + } + } + s.ColId = dc.Id + + // create index + _ = mongo.GetMongoCol(dc.Name).CreateIndex(mongo2.IndexModel{Keys: bson.M{constants.TaskKey: 1}}) + _ = mongo.GetMongoCol(dc.Name).CreateIndex(mongo2.IndexModel{Keys: bson.M{constants.HashKey: 1}}) + } else { + // with id + dc, err := ctx.modelSvc.GetDataCollectionById(s.ColId) + if err != nil { + return err + } + s.ColId = dc.Id + } + return nil +} + +func (ctx *spiderContext) _getGitIgnore(id primitive.ObjectID) (ignore []string, err error) { + workspacePath := viper.GetString("workspace") + filePath := filepath.Join(workspacePath, id.Hex(), ".gitignore") + if !utils.Exists(filePath) { + return nil, nil + } + data, err := os.ReadFile(filePath) + if err != nil { + return nil, trace.TraceError(err) + } + ignore = strings.Split(string(data), "\n") + return ignore, nil +} + +func (ctx *spiderContext) _gitCheckout(gitClient *vcs.GitClient, remote, branch string) (err error) { + if err := gitClient.CheckoutBranch(branch, vcs.WithBranch(branch)); err != nil { + return trace.TraceError(err) + } + + // pull + return ctx._gitPull(gitClient, remote, branch) +} + +func (ctx *spiderContext) _gitPull(gitClient *vcs.GitClient, remote, branch string) (err error) { + // pull + if err := gitClient.Pull( + vcs.WithRemoteNamePull(remote), + vcs.WithBranchNamePull(branch), + ); err != nil { + return trace.TraceError(err) + } + + // reset + if err := gitClient.Reset(); err != nil { + return trace.TraceError(err) + } + + return nil +} + +func (ctx *spiderContext) _getGitClient(id primitive.ObjectID) (gitClient *vcs.GitClient, err error) { + // git + g, err := ctx.modelSvc.GetGitById(id) + if err != nil { + if err != mongo2.ErrNoDocuments { + return nil, trace.TraceError(err) + } + return nil, nil + } + + // git client + workspacePath := viper.GetString("workspace") + gitClient, err = vcs.NewGitClient(vcs.WithPath(filepath.Join(workspacePath, id.Hex()))) + if err != nil { + return nil, err + } + + // set auth + utils.InitGitClientAuth(g, gitClient) + + // remote name + remoteName := vcs.GitRemoteNameOrigin + + // update remote + r, err := gitClient.GetRemote(remoteName) + if err == git.ErrRemoteNotFound { + // remote not exists, create + if _, err := gitClient.CreateRemote(&config.RemoteConfig{ + Name: remoteName, + URLs: []string{g.Url}, + }); err != nil { + return nil, trace.TraceError(err) + } + } else if err == nil { + // remote exists, update if different + if g.Url != r.Config().URLs[0] { + if err := gitClient.DeleteRemote(remoteName); err != nil { + return nil, trace.TraceError(err) + } + if _, err := gitClient.CreateRemote(&config.RemoteConfig{ + Name: remoteName, + URLs: []string{g.Url}, + }); err != nil { + return nil, trace.TraceError(err) + } + } + gitClient.SetRemoteUrl(g.Url) + } else { + // error + return nil, trace.TraceError(err) + } + + // check if head reference exists + _, err = gitClient.GetRepository().Head() + if err == nil { + return gitClient, nil + } + + // align master/main branch + ctx._alignBranch(gitClient) + + return gitClient, nil +} + +func (ctx *spiderContext) _alignBranch(gitClient *vcs.GitClient) { + // current branch + currentBranch, err := gitClient.GetCurrentBranch() + if err != nil { + trace.PrintError(err) + return + } + + // skip if current branch is not master + if currentBranch != vcs.GitBranchNameMaster { + return + } + + // remote refs + refs, err := gitClient.GetRemoteRefs(vcs.GitRemoteNameOrigin) + if err != nil { + trace.PrintError(err) + return + } + + // main branch + defaultRemoteBranch, err := ctx._getDefaultRemoteBranch(refs) + if err != nil || defaultRemoteBranch == "" { + return + } + + // move branch + if err := gitClient.MoveBranch(vcs.GitBranchNameMaster, defaultRemoteBranch); err != nil { + trace.PrintError(err) + } +} + +func (ctx *spiderContext) _getDefaultRemoteBranch(refs []vcs.GitRef) (defaultRemoteBranchName string, err error) { + // remote branch name + for _, r := range refs { + if r.Type != vcs.GitRefTypeBranch { + continue + } + + if r.Name == vcs.GitBranchNameMain { + defaultRemoteBranchName = r.Name + break + } + + if r.Name == vcs.GitBranchNameMaster { + defaultRemoteBranchName = r.Name + continue + } + + if defaultRemoteBranchName == "" { + defaultRemoteBranchName = r.Name + continue + } + } + + return defaultRemoteBranchName, nil +} + +var _spiderCtx *spiderContext + +func newSpiderContext() *spiderContext { + if _spiderCtx != nil { + return _spiderCtx + } + + // context + ctx := &spiderContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + adminSvc interfaces.SpiderAdminService, + ) { + ctx.modelSvc = modelSvc + ctx.adminSvc = adminSvc + }); err != nil { + panic(err) + } + + // model spider service + ctx.modelSpiderSvc = ctx.modelSvc.GetBaseService(interfaces.ModelIdSpider) + + // model spider stat service + ctx.modelSpiderStatSvc = ctx.modelSvc.GetBaseService(interfaces.ModelIdSpiderStat) + + // model task service + ctx.modelTaskSvc = ctx.modelSvc.GetBaseService(interfaces.ModelIdTask) + + // model task stat service + ctx.modelTaskStatSvc = ctx.modelSvc.GetBaseService(interfaces.ModelIdTaskStat) + + _spiderCtx = ctx + + return ctx +} + +func newSpiderController() *spiderController { + actions := getSpiderActions() + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListPostActionControllerDelegate(ControllerIdSpider, modelSvc.GetBaseService(interfaces.ModelIdSpider), actions) + d := NewListPostActionControllerDelegate(ControllerIdSpider, modelSvc.GetBaseService(interfaces.ModelIdSpider), actions) + ctx := newSpiderContext() + + return &spiderController{ + ListActionControllerDelegate: *ctr, + d: *d, + ctx: ctx, + } +} diff --git a/core/controllers/spider_v2.go b/core/controllers/spider_v2.go new file mode 100644 index 00000000..95d28491 --- /dev/null +++ b/core/controllers/spider_v2.go @@ -0,0 +1,1309 @@ +package controllers + +import ( + "errors" + "fmt" + log2 "github.com/apex/log" + "github.com/crawlab-team/crawlab-db/mongo" + vcs "github.com/crawlab-team/crawlab-vcs" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/fs" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/spider/admin" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/config" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "io" + "math" + "os" + "path/filepath" + "strings" + "sync" +) + +func GetSpiderById(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + s, err := service.NewModelServiceV2[models.SpiderV2]().GetById(id) + if errors.Is(err, mongo2.ErrNoDocuments) { + HandleErrorNotFound(c, err) + return + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // stat + s.Stat, err = service.NewModelServiceV2[models.SpiderStatV2]().GetById(s.Id) + if err != nil { + if !errors.Is(err, mongo2.ErrNoDocuments) { + HandleErrorInternalServerError(c, err) + return + } + } + + // data collection + if !s.ColId.IsZero() { + col, err := service.NewModelServiceV2[models.DataCollectionV2]().GetById(s.ColId) + if err != nil { + if !errors.Is(err, mongo2.ErrNoDocuments) { + HandleErrorInternalServerError(c, err) + return + } + } else { + s.ColName = col.Name + } + } + + HandleSuccessWithData(c, s) +} + +func GetSpiderList(c *gin.Context) { + withStats := c.Query("stats") + if withStats == "" { + NewControllerV2[models.SpiderV2]().GetList(c) + return + } + + // params + pagination := MustGetPagination(c) + query := MustGetFilterQuery(c) + sort := MustGetSortOption(c) + + // get list + spiders, err := service.NewModelServiceV2[models.SpiderV2]().GetMany(query, &mongo.FindOptions{ + Sort: sort, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + HandleErrorInternalServerError(c, err) + } + return + } + if len(spiders) == 0 { + HandleSuccessWithListData(c, []models.SpiderV2{}, 0) + return + } + + // ids + var ids []primitive.ObjectID + for _, s := range spiders { + ids = append(ids, s.Id) + } + + // total count + total, err := service.NewModelServiceV2[models.SpiderV2]().Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // stat list + spiderStats, err := service.NewModelServiceV2[models.SpiderStatV2]().GetMany(bson.M{"_id": bson.M{"$in": ids}}, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // cache stat list to dict + dict := map[primitive.ObjectID]models.SpiderStatV2{} + var taskIds []primitive.ObjectID + for _, st := range spiderStats { + if st.Tasks > 0 { + taskCount := int64(st.Tasks) + st.AverageWaitDuration = int64(math.Round(float64(st.WaitDuration) / float64(taskCount))) + st.AverageRuntimeDuration = int64(math.Round(float64(st.RuntimeDuration) / float64(taskCount))) + st.AverageTotalDuration = int64(math.Round(float64(st.TotalDuration) / float64(taskCount))) + } + dict[st.Id] = st + + if !st.LastTaskId.IsZero() { + taskIds = append(taskIds, st.LastTaskId) + } + } + + // task list and stats + var tasks []models.TaskV2 + dictTask := map[primitive.ObjectID]models.TaskV2{} + dictTaskStat := map[primitive.ObjectID]models.TaskStatV2{} + if len(taskIds) > 0 { + // task list + queryTask := bson.M{ + "_id": bson.M{ + "$in": taskIds, + }, + } + tasks, err = service.NewModelServiceV2[models.TaskV2]().GetMany(queryTask, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // task stats list + taskStats, err := service.NewModelServiceV2[models.TaskStatV2]().GetMany(queryTask, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // cache task stats to dict + for _, st := range taskStats { + dictTaskStat[st.Id] = st + } + + // cache task list to dict + for _, t := range tasks { + st, ok := dictTaskStat[t.Id] + if ok { + t.Stat = &st + } + dictTask[t.SpiderId] = t + } + } + + // iterate list again + var data []models.SpiderV2 + for _, s := range spiders { + // spider stat + st, ok := dict[s.Id] + if ok { + s.Stat = &st + + // last task + t, ok := dictTask[s.Id] + if ok { + s.Stat.LastTask = &t + } + } + + // add to list + data = append(data, s) + } + + // response + HandleSuccessWithListData(c, data, total) +} + +func PostSpider(c *gin.Context) { + // bind + var s models.SpiderV2 + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // upsert data collection + if err := upsertSpiderDataCollection(&s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + u := GetUserFromContextV2(c) + + // add + s.SetCreated(u.Id) + s.SetUpdated(u.Id) + id, err := service.NewModelServiceV2[models.SpiderV2]().InsertOne(s) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + s.SetId(id) + + // add stat + st := models.SpiderStatV2{} + st.SetId(id) + st.SetCreated(u.Id) + st.SetUpdated(u.Id) + _, err = service.NewModelServiceV2[models.SpiderStatV2]().InsertOne(st) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // create folder + err = getSpiderFsSvcById(id).CreateDir(".") + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, s) +} + +func PutSpiderById(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // bind + var s models.SpiderV2 + if err := c.ShouldBindJSON(&s); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // upsert data collection + if err := upsertSpiderDataCollection(&s); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + u := GetUserFromContextV2(c) + + modelSvc := service.NewModelServiceV2[models.SpiderV2]() + + // save + s.SetUpdated(u.Id) + err = modelSvc.ReplaceById(id, s) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + _s, err := modelSvc.GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + s = *_s + + HandleSuccessWithData(c, s) +} + +func DeleteSpiderById(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) (err error) { + // delete spider + err = service.NewModelServiceV2[models.SpiderV2]().DeleteById(id) + if err != nil { + return err + } + + // delete spider stat + err = service.NewModelServiceV2[models.SpiderStatV2]().DeleteById(id) + if err != nil { + return err + } + + // related tasks + tasks, err := service.NewModelServiceV2[models.TaskV2]().GetMany(bson.M{"spider_id": id}, nil) + if err != nil { + return err + } + + if len(tasks) == 0 { + return nil + } + + // task ids + var taskIds []primitive.ObjectID + for _, t := range tasks { + taskIds = append(taskIds, t.Id) + } + + // delete related tasks + err = service.NewModelServiceV2[models.TaskV2]().DeleteMany(bson.M{"_id": bson.M{"$in": taskIds}}) + if err != nil { + return err + } + + // delete related task stats + err = service.NewModelServiceV2[models.TaskStatV2]().DeleteMany(bson.M{"_id": bson.M{"$in": taskIds}}) + if err != nil { + return err + } + + // delete tasks logs + wg := sync.WaitGroup{} + wg.Add(len(taskIds)) + for _, id := range taskIds { + go func(id string) { + // delete task logs + logPath := filepath.Join(viper.GetString("log.path"), id) + if err := os.RemoveAll(logPath); err != nil { + log2.Warnf("failed to remove task log directory: %s", logPath) + } + wg.Done() + }(id.Hex()) + } + wg.Wait() + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func DeleteSpiderList(c *gin.Context) { + var payload struct { + Ids []primitive.ObjectID `json:"ids"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) (err error) { + // delete spiders + if err := service.NewModelServiceV2[models.SpiderV2]().DeleteMany(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + // delete spider stats + if err := service.NewModelServiceV2[models.SpiderStatV2]().DeleteMany(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + // related tasks + tasks, err := service.NewModelServiceV2[models.TaskV2]().GetMany(bson.M{"spider_id": bson.M{"$in": payload.Ids}}, nil) + if err != nil { + return err + } + + if len(tasks) == 0 { + return nil + } + + // task ids + var taskIds []primitive.ObjectID + for _, t := range tasks { + taskIds = append(taskIds, t.Id) + } + + // delete related tasks + if err := service.NewModelServiceV2[models.TaskV2]().DeleteMany(bson.M{"_id": bson.M{"$in": taskIds}}); err != nil { + return err + } + + // delete related task stats + if err := service.NewModelServiceV2[models.TaskStatV2]().DeleteMany(bson.M{"_id": bson.M{"$in": taskIds}}); err != nil { + return err + } + + // delete tasks logs + wg := sync.WaitGroup{} + wg.Add(len(taskIds)) + for _, id := range taskIds { + go func(id string) { + // delete task logs + logPath := filepath.Join(viper.GetString("log.path"), id) + if err := os.RemoveAll(logPath); err != nil { + log2.Warnf("failed to remove task log directory: %s", logPath) + } + wg.Done() + }(id.Hex()) + } + wg.Wait() + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func GetSpiderListDir(c *gin.Context) { + path := c.Query("path") + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + files, err := fsSvc.List(path) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + HandleErrorInternalServerError(c, err) + return + } + } + + HandleSuccessWithData(c, files) +} + +func GetSpiderFile(c *gin.Context) { + path := c.Query("path") + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + data, err := fsSvc.GetFile(path) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, string(data)) +} + +func GetSpiderFileInfo(c *gin.Context) { + path := c.Query("path") + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + info, err := fsSvc.GetFileInfo(path) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, info) +} + +func PostSpiderSaveFile(c *gin.Context) { + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + if c.GetHeader("Content-Type") == "application/json" { + var payload struct { + Path string `json:"path"` + Data string `json:"data"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := fsSvc.Save(payload.Path, []byte(payload.Data)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } else { + path, ok := c.GetPostForm("path") + if !ok { + HandleErrorBadRequest(c, errors.New("missing required field 'path'")) + return + } + file, err := c.FormFile("file") + if err != nil { + HandleErrorBadRequest(c, err) + return + } + f, err := file.Open() + if err != nil { + HandleErrorBadRequest(c, err) + return + } + fileData, err := io.ReadAll(f) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := fsSvc.Save(path, fileData); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + HandleSuccess(c) +} + +func PostSpiderSaveFiles(c *gin.Context) { + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + form, err := c.MultipartForm() + if err != nil { + HandleErrorBadRequest(c, err) + return + } + wg := sync.WaitGroup{} + wg.Add(len(form.File)) + for path := range form.File { + go func(path string) { + file, err := c.FormFile(path) + if err != nil { + log2.Warnf("invalid file header: %s", path) + log2.Error(err.Error()) + wg.Done() + return + } + f, err := file.Open() + if err != nil { + log2.Warnf("unable to open file: %s", path) + log2.Error(err.Error()) + wg.Done() + return + } + fileData, err := io.ReadAll(f) + if err != nil { + log2.Warnf("unable to read file: %s", path) + log2.Error(err.Error()) + wg.Done() + return + } + if err := fsSvc.Save(path, fileData); err != nil { + log2.Warnf("unable to save file: %s", path) + log2.Error(err.Error()) + wg.Done() + return + } + wg.Done() + }(path) + } + wg.Wait() + + HandleSuccess(c) +} + +func PostSpiderSaveDir(c *gin.Context) { + var payload struct { + Path string `json:"path"` + NewPath string `json:"new_path"` + Data string `json:"data"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := fsSvc.CreateDir(payload.Path); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func PostSpiderRenameFile(c *gin.Context) { + var payload struct { + Path string `json:"path"` + NewPath string `json:"new_path"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := fsSvc.Rename(payload.Path, payload.NewPath); err != nil { + HandleErrorInternalServerError(c, err) + return + } +} + +func DeleteSpiderFile(c *gin.Context) { + var payload struct { + Path string `json:"path"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + if payload.Path == "~" { + payload.Path = "." + } + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := fsSvc.Delete(payload.Path); err != nil { + HandleErrorInternalServerError(c, err) + return + } + _, err = fsSvc.GetFileInfo(".") + if err != nil { + _ = fsSvc.CreateDir("/") + } + + HandleSuccess(c) +} + +func PostSpiderCopyFile(c *gin.Context) { + var payload struct { + Path string `json:"path"` + NewPath string `json:"new_path"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + fsSvc, err := getSpiderFsSvc(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := fsSvc.Copy(payload.Path, payload.NewPath); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func PostSpiderExport(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + adminSvc, err := admin.GetSpiderAdminServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // zip file path + zipFilePath, err := adminSvc.Export(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // download + c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", zipFilePath)) + c.File(zipFilePath) +} + +func PostSpiderRun(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // options + var opts interfaces.SpiderRunOptions + if err := c.ShouldBindJSON(&opts); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // user + if u := GetUserFromContext(c); u != nil { + opts.UserId = u.GetId() + } + + adminSvc, err := admin.GetSpiderAdminServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // schedule + taskIds, err := adminSvc.Schedule(id, &opts) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, taskIds) +} + +func GetSpiderGit(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // git client + gitClient, err := getSpiderGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // current branch + currentBranch, err := gitClient.GetCurrentBranch() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // branches + branches, err := gitClient.GetBranches() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if branches == nil || len(branches) == 0 && currentBranch != "" { + branches = []vcs.GitRef{{Name: currentBranch}} + } + + // changes + changes, err := gitClient.GetStatus() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // logs + logs, err := gitClient.GetLogsWithRefs() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // ignore + ignore, err := getSpiderGitIgnore(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // git + _git, err := service.NewModelServiceV2[models.GitV2]().GetById(id) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + HandleErrorInternalServerError(c, err) + return + } + } + + // response + res := bson.M{ + "current_branch": currentBranch, + "branches": branches, + "changes": changes, + "logs": logs, + "ignore": ignore, + "git": _git, + } + + HandleSuccessWithData(c, res) +} + +func GetSpiderGitRemoteRefs(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // remote name + remoteName := c.Query("remote") + if remoteName == "" { + remoteName = vcs.GitRemoteNameOrigin + } + + // git client + gitClient, err := getSpiderGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // refs + refs, err := gitClient.GetRemoteRefs(remoteName) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, refs) +} + +func PostSpiderGitCheckout(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // payload + var payload struct { + Paths []string `json:"paths"` + CommitMessage string `json:"commit_message"` + Branch string `json:"branch"` + Tag string `json:"tag"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // git client + gitClient, err := getSpiderGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // branch to pull + var branch string + if payload.Branch == "" { + // by default current branch + branch, err = gitClient.GetCurrentBranch() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + } else { + // payload branch + branch = payload.Branch + } + + // checkout + if err := gitSpiderCheckout(gitClient, constants.GitRemoteNameOrigin, branch); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func PostSpiderGitPull(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // payload + var payload struct { + Paths []string `json:"paths"` + CommitMessage string `json:"commit_message"` + Branch string `json:"branch"` + Tag string `json:"tag"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // git + g, err := service.NewModelServiceV2[models.GitV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // attempt to sync git + adminSvc, err := admin.GetSpiderAdminServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + _ = adminSvc.SyncGitOne(g) + + HandleSuccess(c) +} + +func PostSpiderGitCommit(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // payload + var payload entity.GitPayload + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // git client + gitClient, err := getSpiderGitClient(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // return null if git client is empty + if gitClient == nil { + HandleSuccess(c) + return + } + + // add + for _, p := range payload.Paths { + if err := gitClient.Add(p); err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + // commit + if err := gitClient.Commit(payload.CommitMessage); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // push + if err := gitClient.Push( + vcs.WithRemoteNamePush(vcs.GitRemoteNameOrigin), + ); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func GetSpiderDataSource(c *gin.Context) { + // spider id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider + s, err := service.NewModelServiceV2[models.SpiderV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data source + ds, err := service.NewModelServiceV2[models.DataSourceV2]().GetById(s.DataSourceId) + if err != nil { + if err.Error() == mongo2.ErrNoDocuments.Error() { + HandleSuccess(c) + return + } + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, ds) +} + +func PostSpiderDataSource(c *gin.Context) { + // spider id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // data source id + dsId, err := primitive.ObjectIDFromHex(c.Param("ds_id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // spider + s, err := service.NewModelServiceV2[models.SpiderV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // data source + if !dsId.IsZero() { + _, err = service.NewModelServiceV2[models.DataSourceV2]().GetById(dsId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + } + + // save data source id + u := GetUserFromContextV2(c) + s.DataSourceId = dsId + s.SetUpdatedBy(u.Id) + _, err = service.NewModelServiceV2[models.SpiderV2]().InsertOne(*s) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func getSpiderFsSvc(c *gin.Context) (svc interfaces.FsServiceV2, err error) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + return nil, err + } + + workspacePath := viper.GetString("workspace") + fsSvc := fs.NewFsServiceV2(filepath.Join(workspacePath, id.Hex())) + + return fsSvc, nil +} + +func getSpiderFsSvcById(id primitive.ObjectID) interfaces.FsServiceV2 { + workspacePath := viper.GetString("workspace") + fsSvc := fs.NewFsServiceV2(filepath.Join(workspacePath, id.Hex())) + return fsSvc +} + +func getSpiderGitClient(id primitive.ObjectID) (client *vcs.GitClient, err error) { + // git + g, err := service.NewModelServiceV2[models.GitV2]().GetById(id) + if err != nil { + if !errors.Is(err, mongo2.ErrNoDocuments) { + return nil, trace.TraceError(err) + } + return nil, nil + } + + // git client + workspacePath := viper.GetString("workspace") + client, err = vcs.NewGitClient(vcs.WithPath(filepath.Join(workspacePath, id.Hex()))) + if err != nil { + return nil, err + } + + // set auth + utils.InitGitClientAuthV2(g, client) + + // remote name + remoteName := vcs.GitRemoteNameOrigin + + // update remote + r, err := client.GetRemote(remoteName) + if errors.Is(err, git.ErrRemoteNotFound) { + // remote not exists, create + if _, err := client.CreateRemote(&config.RemoteConfig{ + Name: remoteName, + URLs: []string{g.Url}, + }); err != nil { + return nil, trace.TraceError(err) + } + } else if err == nil { + // remote exists, update if different + if g.Url != r.Config().URLs[0] { + if err := client.DeleteRemote(remoteName); err != nil { + return nil, trace.TraceError(err) + } + if _, err := client.CreateRemote(&config.RemoteConfig{ + Name: remoteName, + URLs: []string{g.Url}, + }); err != nil { + return nil, trace.TraceError(err) + } + } + client.SetRemoteUrl(g.Url) + } else { + // error + return nil, trace.TraceError(err) + } + + // check if head reference exists + _, err = client.GetRepository().Head() + if err == nil { + return client, nil + } + + // align master/main branch + alignSpiderGitBranch(client) + + return client, nil +} + +func alignSpiderGitBranch(gitClient *vcs.GitClient) { + // current branch + currentBranch, err := gitClient.GetCurrentBranch() + if err != nil { + trace.PrintError(err) + return + } + + // skip if current branch is not master + if currentBranch != vcs.GitBranchNameMaster { + return + } + + // remote refs + refs, err := gitClient.GetRemoteRefs(vcs.GitRemoteNameOrigin) + if err != nil { + trace.PrintError(err) + return + } + + // main branch + defaultRemoteBranch, err := getSpiderDefaultRemoteBranch(refs) + if err != nil || defaultRemoteBranch == "" { + return + } + + // move branch + if err := gitClient.MoveBranch(vcs.GitBranchNameMaster, defaultRemoteBranch); err != nil { + trace.PrintError(err) + } +} + +func getSpiderDefaultRemoteBranch(refs []vcs.GitRef) (defaultRemoteBranchName string, err error) { + // remote branch name + for _, r := range refs { + if r.Type != vcs.GitRefTypeBranch { + continue + } + + if r.Name == vcs.GitBranchNameMain { + defaultRemoteBranchName = r.Name + break + } + + if r.Name == vcs.GitBranchNameMaster { + defaultRemoteBranchName = r.Name + continue + } + + if defaultRemoteBranchName == "" { + defaultRemoteBranchName = r.Name + continue + } + } + + return defaultRemoteBranchName, nil +} + +func getSpiderGitIgnore(id primitive.ObjectID) (ignore []string, err error) { + workspacePath := viper.GetString("workspace") + filePath := filepath.Join(workspacePath, id.Hex(), ".gitignore") + if !utils.Exists(filePath) { + return nil, nil + } + data, err := os.ReadFile(filePath) + if err != nil { + return nil, trace.TraceError(err) + } + ignore = strings.Split(string(data), "\n") + return ignore, nil +} + +func gitSpiderCheckout(gitClient *vcs.GitClient, remote, branch string) (err error) { + if err := gitClient.CheckoutBranch(branch, vcs.WithBranch(branch)); err != nil { + return trace.TraceError(err) + } + + // pull + return spiderGitPull(gitClient, remote, branch) +} + +func spiderGitPull(gitClient *vcs.GitClient, remote, branch string) (err error) { + // pull + if err := gitClient.Pull( + vcs.WithRemoteNamePull(remote), + vcs.WithBranchNamePull(branch), + ); err != nil { + return trace.TraceError(err) + } + + // reset + if err := gitClient.Reset(); err != nil { + return trace.TraceError(err) + } + + return nil +} + +func upsertSpiderDataCollection(s *models.SpiderV2) (err error) { + modelSvc := service.NewModelServiceV2[models.DataCollectionV2]() + if s.ColId.IsZero() { + // validate + if s.ColName == "" { + return errors.New("data collection name is required") + } + // no id + dc, err := modelSvc.GetOne(bson.M{"name": s.ColName}, nil) + if err != nil { + if errors.Is(err, mongo2.ErrNoDocuments) { + // not exists, add new + dc = &models.DataCollectionV2{Name: s.ColName} + dcId, err := modelSvc.InsertOne(*dc) + if err != nil { + return err + } + dc.SetId(dcId) + } else { + // error + return err + } + } + s.ColId = dc.Id + + // create index + _ = mongo.GetMongoCol(dc.Name).CreateIndex(mongo2.IndexModel{Keys: bson.M{constants.TaskKey: 1}}) + _ = mongo.GetMongoCol(dc.Name).CreateIndex(mongo2.IndexModel{Keys: bson.M{constants.HashKey: 1}}) + } else { + // with id + dc, err := modelSvc.GetById(s.ColId) + if err != nil { + return err + } + s.ColId = dc.Id + } + return nil +} diff --git a/core/controllers/spider_v2_test.go b/core/controllers/spider_v2_test.go new file mode 100644 index 00000000..69c50f28 --- /dev/null +++ b/core/controllers/spider_v2_test.go @@ -0,0 +1,249 @@ +package controllers_test + +import ( + "bytes" + "encoding/json" + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/middlewares" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" + "net/http/httptest" + "testing" +) + +func TestCreateSpider(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + gin.SetMode(gin.TestMode) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.POST("/spiders", controllers.PostSpider) + + payload := models.SpiderV2{ + Name: "Test Spider", + ColName: "test_spiders", + } + jsonValue, _ := json.Marshal(payload) + req, _ := http.NewRequest("POST", "/spiders", bytes.NewBuffer(jsonValue)) + req.Header.Set("Authorization", TestToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + var response controllers.Response[models.SpiderV2] + err := json.Unmarshal(resp.Body.Bytes(), &response) + require.Nil(t, err) + assert.False(t, response.Data.Id.IsZero()) + assert.Equal(t, payload.Name, response.Data.Name) + assert.False(t, response.Data.ColId.IsZero()) +} + +func TestGetSpiderById(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + gin.SetMode(gin.TestMode) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.GET("/spiders/:id", controllers.GetSpiderById) + + model := models.SpiderV2{ + Name: "Test Spider", + ColName: "test_spiders", + } + id, err := service.NewModelServiceV2[models.SpiderV2]().InsertOne(model) + require.Nil(t, err) + ts := models.SpiderStatV2{} + ts.SetId(id) + _, err = service.NewModelServiceV2[models.SpiderStatV2]().InsertOne(ts) + require.Nil(t, err) + + req, _ := http.NewRequest("GET", "/spiders/"+id.Hex(), nil) + req.Header.Set("Authorization", TestToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + var response controllers.Response[models.SpiderV2] + err = json.Unmarshal(resp.Body.Bytes(), &response) + require.Nil(t, err) + assert.Equal(t, model.Name, response.Data.Name) +} + +func TestUpdateSpiderById(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + gin.SetMode(gin.TestMode) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.PUT("/spiders/:id", controllers.PutSpiderById) + + model := models.SpiderV2{ + Name: "Test Spider", + ColName: "test_spiders", + } + id, err := service.NewModelServiceV2[models.SpiderV2]().InsertOne(model) + require.Nil(t, err) + ts := models.SpiderStatV2{} + ts.SetId(id) + _, err = service.NewModelServiceV2[models.SpiderStatV2]().InsertOne(ts) + require.Nil(t, err) + + spiderId := id.Hex() + payload := models.SpiderV2{ + Name: "Updated Spider", + ColName: "test_spider", + } + payload.SetId(id) + jsonValue, _ := json.Marshal(payload) + req, _ := http.NewRequest("PUT", "/spiders/"+spiderId, bytes.NewBuffer(jsonValue)) + req.Header.Set("Authorization", TestToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + var response controllers.Response[models.SpiderV2] + err = json.Unmarshal(resp.Body.Bytes(), &response) + require.Nil(t, err) + assert.Equal(t, payload.Name, response.Data.Name) + + svc := service.NewModelServiceV2[models.SpiderV2]() + resModel, err := svc.GetById(id) + require.Nil(t, err) + assert.Equal(t, payload.Name, resModel.Name) +} + +func TestDeleteSpiderById(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + gin.SetMode(gin.TestMode) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.DELETE("/spiders/:id", controllers.DeleteSpiderById) + + model := models.SpiderV2{ + Name: "Test Spider", + ColName: "test_spiders", + } + id, err := service.NewModelServiceV2[models.SpiderV2]().InsertOne(model) + require.Nil(t, err) + ts := models.SpiderStatV2{} + ts.SetId(id) + _, err = service.NewModelServiceV2[models.SpiderStatV2]().InsertOne(ts) + require.Nil(t, err) + task := models.TaskV2{} + task.SpiderId = id + taskId, err := service.NewModelServiceV2[models.TaskV2]().InsertOne(task) + require.Nil(t, err) + taskStat := models.TaskStatV2{} + taskStat.SetId(taskId) + _, err = service.NewModelServiceV2[models.TaskStatV2]().InsertOne(taskStat) + require.Nil(t, err) + + req, _ := http.NewRequest("DELETE", "/spiders/"+id.Hex(), nil) + req.Header.Set("Authorization", TestToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + _, err = service.NewModelServiceV2[models.SpiderV2]().GetById(id) + assert.NotNil(t, err) + _, err = service.NewModelServiceV2[models.SpiderStatV2]().GetById(id) + assert.NotNil(t, err) + taskCount, err := service.NewModelServiceV2[models.TaskV2]().Count(bson.M{"spider_id": id}) + require.Nil(t, err) + assert.Equal(t, 0, taskCount) + taskStatCount, err := service.NewModelServiceV2[models.TaskStatV2]().Count(bson.M{"_id": taskId}) + require.Nil(t, err) + assert.Equal(t, 0, taskStatCount) + +} + +func TestDeleteSpiderList(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + gin.SetMode(gin.TestMode) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.DELETE("/spiders", controllers.DeleteSpiderList) + + modelList := []models.SpiderV2{ + { + Name: "Test Name 1", + ColName: "test_spiders", + }, { + Name: "Test Name 2", + ColName: "test_spiders", + }, + } + var ids []primitive.ObjectID + var taskIds []primitive.ObjectID + for _, model := range modelList { + id, err := service.NewModelServiceV2[models.SpiderV2]().InsertOne(model) + require.Nil(t, err) + ts := models.SpiderStatV2{} + ts.SetId(id) + _, err = service.NewModelServiceV2[models.SpiderStatV2]().InsertOne(ts) + require.Nil(t, err) + task := models.TaskV2{} + task.SpiderId = id + taskId, err := service.NewModelServiceV2[models.TaskV2]().InsertOne(task) + require.Nil(t, err) + taskStat := models.TaskStatV2{} + taskStat.SetId(taskId) + _, err = service.NewModelServiceV2[models.TaskStatV2]().InsertOne(taskStat) + require.Nil(t, err) + ids = append(ids, id) + taskIds = append(taskIds, taskId) + } + + payload := struct { + Ids []primitive.ObjectID `json:"ids"` + }{ + Ids: ids, + } + jsonValue, _ := json.Marshal(payload) + req, _ := http.NewRequest("DELETE", "/spiders", bytes.NewBuffer(jsonValue)) + req.Header.Set("Authorization", TestToken) + resp := httptest.NewRecorder() + + router.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + spiderCount, err := service.NewModelServiceV2[models.SpiderV2]().Count(bson.M{"_id": bson.M{"$in": ids}}) + require.Nil(t, err) + assert.Equal(t, 0, spiderCount) + spiderStatCount, err := service.NewModelServiceV2[models.SpiderStatV2]().Count(bson.M{"_id": bson.M{"$in": ids}}) + require.Nil(t, err) + assert.Equal(t, 0, spiderStatCount) + taskCount, err := service.NewModelServiceV2[models.TaskV2]().Count(bson.M{"_id": bson.M{"$in": taskIds}}) + require.Nil(t, err) + assert.Equal(t, 0, taskCount) + taskStatCount, err := service.NewModelServiceV2[models.TaskStatV2]().Count(bson.M{"_id": bson.M{"$in": taskIds}}) + require.Nil(t, err) + assert.Equal(t, 0, taskStatCount) +} diff --git a/core/controllers/stats.go b/core/controllers/stats.go new file mode 100644 index 00000000..3d474956 --- /dev/null +++ b/core/controllers/stats.go @@ -0,0 +1,87 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "net/http" + "time" +) + +var StatsController ActionController + +func getStatsActions() []Action { + statsCtx := newStatsContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/overview", + HandlerFunc: statsCtx.getOverview, + }, + { + Method: http.MethodGet, + Path: "/daily", + HandlerFunc: statsCtx.getDaily, + }, + { + Method: http.MethodGet, + Path: "/tasks", + HandlerFunc: statsCtx.getTasks, + }, + } +} + +type statsContext struct { + statsSvc interfaces.StatsService + defaultQuery bson.M +} + +func (svc *statsContext) getOverview(c *gin.Context) { + data, err := svc.statsSvc.GetOverviewStats(svc.defaultQuery) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, data) +} + +func (svc *statsContext) getDaily(c *gin.Context) { + data, err := svc.statsSvc.GetDailyStats(svc.defaultQuery) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, data) +} + +func (svc *statsContext) getTasks(c *gin.Context) { + data, err := svc.statsSvc.GetTaskStats(svc.defaultQuery) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, data) +} + +func newStatsContext() *statsContext { + // context + ctx := &statsContext{ + defaultQuery: bson.M{ + "create_ts": bson.M{ + "$gte": time.Now().Add(-30 * 24 * time.Hour), + }, + }, + } + + // dependency injection + if err := container.GetContainer().Invoke(func( + statsSvc interfaces.StatsService, + ) { + ctx.statsSvc = statsSvc + }); err != nil { + panic(err) + } + + return ctx +} diff --git a/core/controllers/stats_v2.go b/core/controllers/stats_v2.go new file mode 100644 index 00000000..cf7bda03 --- /dev/null +++ b/core/controllers/stats_v2.go @@ -0,0 +1,41 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/stats" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "time" +) + +var statsDefaultQuery = bson.M{ + "create_ts": bson.M{ + "$gte": time.Now().Add(-30 * 24 * time.Hour), + }, +} + +func GetStatsOverview(c *gin.Context) { + data, err := stats.GetStatsService().GetOverviewStats(statsDefaultQuery) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, data) +} + +func GetStatsDaily(c *gin.Context) { + data, err := stats.GetStatsService().GetDailyStats(statsDefaultQuery) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, data) +} + +func GetStatsTasks(c *gin.Context) { + data, err := stats.GetStatsService().GetTaskStats(statsDefaultQuery) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, data) +} diff --git a/core/controllers/sync.go b/core/controllers/sync.go new file mode 100644 index 00000000..674bff76 --- /dev/null +++ b/core/controllers/sync.go @@ -0,0 +1,57 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "net/http" + "path/filepath" +) + +var SyncController ActionController + +func getSyncActions() []Action { + var ctx = newSyncContext() + return []Action{ + { + Method: http.MethodGet, + Path: "/:id/scan", + HandlerFunc: ctx.scan, + }, + { + Method: http.MethodGet, + Path: "/:id/download", + HandlerFunc: ctx.download, + }, + } +} + +type syncContext struct { +} + +func (ctx *syncContext) scan(c *gin.Context) { + id := c.Param("id") + dir := ctx._getDir(id) + files, err := utils.ScanDirectory(dir) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + c.AbortWithStatusJSON(http.StatusOK, files) +} + +func (ctx *syncContext) download(c *gin.Context) { + id := c.Param("id") + filePath := c.Query("path") + dir := ctx._getDir(id) + c.File(filepath.Join(dir, filePath)) +} + +func (ctx *syncContext) _getDir(id string) string { + workspacePath := viper.GetString("workspace") + return filepath.Join(workspacePath, id) +} + +func newSyncContext() syncContext { + return syncContext{} +} diff --git a/core/controllers/system_info.go b/core/controllers/system_info.go new file mode 100644 index 00000000..a89edc15 --- /dev/null +++ b/core/controllers/system_info.go @@ -0,0 +1,28 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/entity" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "net/http" +) + +func getSystemInfo(c *gin.Context) { + info := &entity.SystemInfo{ + Edition: viper.GetString("info.edition"), + Version: viper.GetString("info.version"), + } + HandleSuccessWithData(c, info) +} + +func getSystemInfoActions() []Action { + return []Action{ + { + Path: "", + Method: http.MethodGet, + HandlerFunc: getSystemInfo, + }, + } +} + +var SystemInfoController ActionController diff --git a/core/controllers/system_info_v2.go b/core/controllers/system_info_v2.go new file mode 100644 index 00000000..fc01ea6f --- /dev/null +++ b/core/controllers/system_info_v2.go @@ -0,0 +1,15 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/entity" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" +) + +func GetSystemInfo(c *gin.Context) { + info := &entity.SystemInfo{ + Edition: viper.GetString("info.edition"), + Version: viper.GetString("info.version"), + } + HandleSuccessWithData(c, info) +} diff --git a/core/controllers/tag.go b/core/controllers/tag.go new file mode 100644 index 00000000..3f74abf2 --- /dev/null +++ b/core/controllers/tag.go @@ -0,0 +1,3 @@ +package controllers + +var TagController ListController diff --git a/core/controllers/task.go b/core/controllers/task.go new file mode 100644 index 00000000..7d9015f2 --- /dev/null +++ b/core/controllers/task.go @@ -0,0 +1,534 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + delegate2 "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/result" + "github.com/crawlab-team/crawlab/core/task/log" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "net/http" + "strings" +) + +var TaskController *taskController + +func getTaskActions() []Action { + taskCtx := newTaskContext() + return []Action{ + { + Method: http.MethodPost, + Path: "/run", + HandlerFunc: taskCtx.run, + }, + { + Method: http.MethodPost, + Path: "/:id/restart", + HandlerFunc: taskCtx.restart, + }, + { + Method: http.MethodPost, + Path: "/:id/cancel", + HandlerFunc: taskCtx.cancel, + }, + { + Method: http.MethodGet, + Path: "/:id/logs", + HandlerFunc: taskCtx.getLogs, + }, + { + Method: http.MethodGet, + Path: "/:id/data", + HandlerFunc: taskCtx.getData, + }, + } +} + +type taskController struct { + ListActionControllerDelegate + d ListActionControllerDelegate + ctx *taskContext +} + +func (ctr *taskController) Get(c *gin.Context) { + ctr.ctx.getWithStatsSpider(c) +} + +func (ctr *taskController) Delete(c *gin.Context) { + if err := ctr.ctx._delete(c); err != nil { + return + } + HandleSuccess(c) +} + +func (ctr *taskController) GetList(c *gin.Context) { + withStats := c.Query("stats") + if withStats == "" { + ctr.d.GetList(c) + return + } + ctr.ctx.getListWithStats(c) +} + +func (ctr *taskController) DeleteList(c *gin.Context) { + if err := ctr.ctx._deleteList(c); err != nil { + return + } + HandleSuccess(c) +} + +type taskContext struct { + modelSvc service.ModelService + modelTaskSvc interfaces.ModelBaseService + modelTaskStatSvc interfaces.ModelBaseService + adminSvc interfaces.SpiderAdminService + schedulerSvc interfaces.TaskSchedulerService + l log.Driver +} + +func (ctx *taskContext) run(c *gin.Context) { + // task + var t models.Task + if err := c.ShouldBindJSON(&t); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // validate spider id + if t.GetSpiderId().IsZero() { + HandleErrorBadRequest(c, errors.ErrorTaskEmptySpiderId) + return + } + + // spider + s, err := ctx.modelSvc.GetSpiderById(t.GetSpiderId()) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // options + opts := &interfaces.SpiderRunOptions{ + Mode: t.Mode, + NodeIds: t.NodeIds, + Cmd: t.Cmd, + Param: t.Param, + Priority: t.Priority, + } + + // user + if u := GetUserFromContext(c); u != nil { + opts.UserId = u.GetId() + } + + // run + taskIds, err := ctx.adminSvc.Schedule(s.GetId(), opts) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, taskIds) +} + +func (ctx *taskContext) restart(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := ctx.modelSvc.GetTaskById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // options + opts := &interfaces.SpiderRunOptions{ + Mode: t.Mode, + NodeIds: t.NodeIds, + Cmd: t.Cmd, + Param: t.Param, + Priority: t.Priority, + } + + // user + if u := GetUserFromContext(c); u != nil { + opts.UserId = u.GetId() + } + + // run + taskIds, err := ctx.adminSvc.Schedule(t.SpiderId, opts) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, taskIds) +} + +func (ctx *taskContext) cancel(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := ctx.modelSvc.GetTaskById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // validate + if !utils.IsCancellable(t.Status) { + HandleErrorInternalServerError(c, errors.ErrorControllerNotCancellable) + return + } + + // cancel + if err := ctx.schedulerSvc.Cancel(id, GetUserFromContext(c)); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func (ctx *taskContext) getLogs(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // pagination + p, err := GetPagination(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // logs + logs, err := ctx.l.Find(id.Hex(), "", (p.Page-1)*p.Size, p.Size) + if err != nil { + if strings.HasSuffix(err.Error(), "Status:404 Not Found") { + HandleSuccess(c) + return + } + HandleErrorInternalServerError(c, err) + return + } + total, err := ctx.l.Count(id.Hex(), "") + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithListData(c, logs, total) +} + +func (ctx *taskContext) getListWithStats(c *gin.Context) { + // params + pagination := MustGetPagination(c) + query := MustGetFilterQuery(c) + sort := MustGetSortOption(c) + + // get list + list, err := ctx.modelTaskSvc.GetList(query, &mongo.FindOptions{ + Sort: sort, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if err == mongo2.ErrNoDocuments { + HandleErrorNotFound(c, err) + } else { + HandleErrorInternalServerError(c, err) + } + return + } + + // check empty list + if len(list.GetModels()) == 0 { + HandleSuccessWithListData(c, nil, 0) + return + } + + // ids + var ids []primitive.ObjectID + for _, d := range list.GetModels() { + t := d.(interfaces.Model) + ids = append(ids, t.GetId()) + } + + // total count + total, err := ctx.modelTaskSvc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // stat list + query = bson.M{ + "_id": bson.M{ + "$in": ids, + }, + } + stats, err := ctx.modelSvc.GetTaskStatList(query, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // cache stat list to dict + dict := map[primitive.ObjectID]models.TaskStat{} + for _, s := range stats { + dict[s.GetId()] = s + } + + // iterate list again + var data []interface{} + for _, d := range list.GetModels() { + t := d.(*models.Task) + s, ok := dict[t.GetId()] + if ok { + t.Stat = &s + } + data = append(data, *t) + } + + // response + HandleSuccessWithListData(c, data, total) +} + +func (ctx *taskContext) getWithStatsSpider(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := ctx.modelSvc.GetTaskById(id) + if err == mongo2.ErrNoDocuments { + HandleErrorNotFound(c, err) + return + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // spider + t.Spider, _ = ctx.modelSvc.GetSpiderById(t.SpiderId) + + // skip if task status is pending + if t.Status == constants.TaskStatusPending { + HandleSuccessWithData(c, t) + return + } + + // task stat + t.Stat, _ = ctx.modelSvc.GetTaskStatById(id) + + HandleSuccessWithData(c, t) +} + +func (ctx *taskContext) getData(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // pagination + p, err := GetPagination(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := ctx.modelSvc.GetTaskById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // result service + resultSvc, err := result.GetResultService(t.SpiderId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // query + query := generic.ListQuery{ + generic.ListQueryCondition{ + Key: constants.TaskKey, + Op: generic.OpEqual, + Value: t.Id, + }, + } + + // list + data, err := resultSvc.List(query, &generic.ListOptions{ + Skip: (p.Page - 1) * p.Size, + Limit: p.Size, + Sort: []generic.ListSort{{"_id", generic.SortDirectionDesc}}, + }) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // total + total, err := resultSvc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithListData(c, data, total) +} + +func (ctx *taskContext) _delete(c *gin.Context) (err error) { + id := c.Param("id") + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) (err error) { + // delete task + task, err := ctx.modelSvc.GetTaskById(oid) + if err != nil { + return err + } + if err := delegate2.NewModelDelegate(task, GetUserFromContext(c)).Delete(); err != nil { + return err + } + + // delete task stat + taskStat, err := ctx.modelSvc.GetTaskStatById(oid) + if err != nil { + return err + } + if err := delegate2.NewModelDelegate(taskStat, GetUserFromContext(c)).Delete(); err != nil { + return err + } + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return err + } + + return nil +} + +func (ctx *taskContext) _deleteList(c *gin.Context) (err error) { + payload, err := NewJsonBinder(ControllerIdTask).BindBatchRequestPayload(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) error { + // delete tasks + if err := ctx.modelTaskSvc.DeleteList(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + // delete task stats + if err := ctx.modelTaskStatSvc.DeleteList(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return err + } + + return nil +} + +func newTaskContext() *taskContext { + // context + ctx := &taskContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + adminSvc interfaces.SpiderAdminService, + schedulerSvc interfaces.TaskSchedulerService, + ) { + ctx.modelSvc = modelSvc + ctx.adminSvc = adminSvc + ctx.schedulerSvc = schedulerSvc + }); err != nil { + panic(err) + } + + // model task service + ctx.modelTaskSvc = ctx.modelSvc.GetBaseService(interfaces.ModelIdTask) + + // model task stat service + ctx.modelTaskStatSvc = ctx.modelSvc.GetBaseService(interfaces.ModelIdTaskStat) + + // log driver + l, err := log.GetLogDriver(log.DriverTypeFile) + if err != nil { + panic(err) + } + ctx.l = l + + return ctx +} + +func newTaskController() *taskController { + actions := getTaskActions() + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListPostActionControllerDelegate(ControllerIdTask, modelSvc.GetBaseService(interfaces.ModelIdTask), actions) + d := NewListPostActionControllerDelegate(ControllerIdTask, modelSvc.GetBaseService(interfaces.ModelIdTask), actions) + ctx := newTaskContext() + + return &taskController{ + ListActionControllerDelegate: *ctr, + d: *d, + ctx: ctx, + } +} diff --git a/core/controllers/task_v2.go b/core/controllers/task_v2.go new file mode 100644 index 00000000..ab8619cf --- /dev/null +++ b/core/controllers/task_v2.go @@ -0,0 +1,465 @@ +package controllers + +import ( + "errors" + log2 "github.com/apex/log" + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/result" + "github.com/crawlab-team/crawlab/core/spider/admin" + "github.com/crawlab-team/crawlab/core/task/log" + "github.com/crawlab-team/crawlab/core/task/scheduler" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "os" + "path/filepath" + "strings" + "sync" +) + +func GetTaskById(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := service.NewModelServiceV2[models.TaskV2]().GetById(id) + if errors.Is(err, mongo2.ErrNoDocuments) { + HandleErrorNotFound(c, err) + return + } + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // spider + t.Spider, _ = service.NewModelServiceV2[models.SpiderV2]().GetById(t.SpiderId) + + // skip if task status is pending + if t.Status == constants.TaskStatusPending { + HandleSuccessWithData(c, t) + return + } + + // task stat + t.Stat, _ = service.NewModelServiceV2[models.TaskStatV2]().GetById(id) + + HandleSuccessWithData(c, t) +} + +func GetTaskList(c *gin.Context) { + withStats := c.Query("stats") + if withStats == "" { + NewControllerV2[models.TaskV2]().GetList(c) + return + } + + // params + pagination := MustGetPagination(c) + query := MustGetFilterQuery(c) + sort := MustGetSortOption(c) + + // get list + list, err := service.NewModelServiceV2[models.TaskV2]().GetMany(query, &mongo.FindOptions{ + Sort: sort, + Skip: pagination.Size * (pagination.Page - 1), + Limit: pagination.Size, + }) + if err != nil { + if errors.Is(err, mongo2.ErrNoDocuments) { + HandleErrorNotFound(c, err) + } else { + HandleErrorInternalServerError(c, err) + } + return + } + + // check empty list + if len(list) == 0 { + HandleSuccessWithListData(c, nil, 0) + return + } + + // ids + var ids []primitive.ObjectID + for _, t := range list { + ids = append(ids, t.Id) + } + + // total count + total, err := service.NewModelServiceV2[models.TaskV2]().Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // stat list + query = bson.M{ + "_id": bson.M{ + "$in": ids, + }, + } + stats, err := service.NewModelServiceV2[models.TaskStatV2]().GetMany(query, nil) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // cache stat list to dict + dict := map[primitive.ObjectID]models.TaskStatV2{} + for _, s := range stats { + dict[s.Id] = s + } + + // iterate list again + for i, t := range list { + ts, ok := dict[t.Id] + if ok { + list[i].Stat = &ts + } + } + + // response + HandleSuccessWithListData(c, list, total) +} + +func DeleteTaskById(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // delete in db + if err := mongo.RunTransaction(func(context mongo2.SessionContext) (err error) { + // delete task + _, err = service.NewModelServiceV2[models.TaskV2]().GetById(id) + if err != nil { + return err + } + err = service.NewModelServiceV2[models.TaskV2]().DeleteById(id) + if err != nil { + return err + } + + // delete task stat + _, err = service.NewModelServiceV2[models.TaskStatV2]().GetById(id) + if err != nil { + log2.Warnf("delete task stat error: %s", err.Error()) + return nil + } + err = service.NewModelServiceV2[models.TaskStatV2]().DeleteById(id) + if err != nil { + log2.Warnf("delete task stat error: %s", err.Error()) + return nil + } + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // delete task logs + logPath := filepath.Join(viper.GetString("log.path"), id.Hex()) + if err := os.RemoveAll(logPath); err != nil { + log2.Warnf("failed to remove task log directory: %s", logPath) + } + + HandleSuccess(c) +} + +func DeleteList(c *gin.Context) { + var payload struct { + Ids []primitive.ObjectID `json:"ids"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + if err := mongo.RunTransaction(func(context mongo2.SessionContext) error { + // delete tasks + if err := service.NewModelServiceV2[models.TaskV2]().DeleteMany(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + return err + } + + // delete task stats + if err := service.NewModelServiceV2[models.TaskV2]().DeleteMany(bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + }); err != nil { + log2.Warnf("delete task stat error: %s", err.Error()) + return nil + } + + return nil + }); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // delete tasks logs + wg := sync.WaitGroup{} + wg.Add(len(payload.Ids)) + for _, id := range payload.Ids { + go func(id string) { + // delete task logs + logPath := filepath.Join(viper.GetString("log.path"), id) + if err := os.RemoveAll(logPath); err != nil { + log2.Warnf("failed to remove task log directory: %s", logPath) + } + wg.Done() + }(id.Hex()) + } + wg.Wait() + + HandleSuccess(c) +} + +func PostTaskRun(c *gin.Context) { + // task + var t models.TaskV2 + if err := c.ShouldBindJSON(&t); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // validate spider id + if t.SpiderId.IsZero() { + HandleErrorBadRequest(c, errors.New("spider id is required")) + return + } + + // spider + s, err := service.NewModelServiceV2[models.SpiderV2]().GetById(t.SpiderId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // options + opts := &interfaces.SpiderRunOptions{ + Mode: t.Mode, + NodeIds: t.NodeIds, + Cmd: t.Cmd, + Param: t.Param, + Priority: t.Priority, + } + + // user + if u := GetUserFromContextV2(c); u != nil { + opts.UserId = u.Id + } + + // run + adminSvc, err := admin.GetSpiderAdminServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + taskIds, err := adminSvc.Schedule(s.Id, opts) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, taskIds) + +} + +func PostTaskRestart(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := service.NewModelServiceV2[models.TaskV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // options + opts := &interfaces.SpiderRunOptions{ + Mode: t.Mode, + NodeIds: t.NodeIds, + Cmd: t.Cmd, + Param: t.Param, + Priority: t.Priority, + } + + // user + if u := GetUserFromContextV2(c); u != nil { + opts.UserId = u.Id + } + + // run + adminSvc, err := admin.GetSpiderAdminServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + taskIds, err := adminSvc.Schedule(t.SpiderId, opts) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, taskIds) +} + +func PostTaskCancel(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := service.NewModelServiceV2[models.TaskV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // validate + if !utils.IsCancellable(t.Status) { + HandleErrorInternalServerError(c, errors.New("task is not cancellable")) + return + } + + u := GetUserFromContextV2(c) + + // cancel + schedulerSvc, err := scheduler.GetTaskSchedulerServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := schedulerSvc.Cancel(id, u.Id); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccess(c) +} + +func GetTaskLogs(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // pagination + p, err := GetPagination(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // logs + logDriver, err := log.GetFileLogDriver() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + logs, err := logDriver.Find(id.Hex(), "", (p.Page-1)*p.Size, p.Size) + if err != nil { + if strings.HasSuffix(err.Error(), "Status:404 Not Found") { + HandleSuccess(c) + return + } + HandleErrorInternalServerError(c, err) + return + } + total, err := logDriver.Count(id.Hex(), "") + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithListData(c, logs, total) +} + +func GetTaskData(c *gin.Context) { + // id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // pagination + p, err := GetPagination(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // task + t, err := service.NewModelServiceV2[models.TaskV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // result service + resultSvc, err := result.GetResultService(t.SpiderId) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // query + query := generic.ListQuery{ + generic.ListQueryCondition{ + Key: constants.TaskKey, + Op: generic.OpEqual, + Value: t.Id, + }, + } + + // list + data, err := resultSvc.List(query, &generic.ListOptions{ + Skip: (p.Page - 1) * p.Size, + Limit: p.Size, + Sort: []generic.ListSort{{"_id", generic.SortDirectionDesc}}, + }) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // total + total, err := resultSvc.Count(query) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithListData(c, data, total) +} diff --git a/core/controllers/test/base.go b/core/controllers/test/base.go new file mode 100644 index 00000000..fe8b1835 --- /dev/null +++ b/core/controllers/test/base.go @@ -0,0 +1,104 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/routes" + "github.com/crawlab-team/go-trace" + "github.com/gavv/httpexpect/v2" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" + "go.uber.org/dig" + "net/http/httptest" + "testing" + "time" +) + +func init() { + var err error + T, err = NewTest() + if err != nil { + panic(err) + } +} + +type Test struct { + // dependencies + modelSvc service.ModelService + + // internals + app *gin.Engine + svr *httptest.Server + + // test data + TestUsername string + TestPassword string + TestToken string +} + +func (t *Test) Setup(t2 *testing.T) { + //if err := controllers.InitControllers(); err != nil { + // panic(err) + //} + //t2.Cleanup(t.Cleanup) +} + +func (t *Test) Cleanup() { + _ = t.modelSvc.DropAll() + time.Sleep(200 * time.Millisecond) +} + +func (t *Test) NewExpect(t2 *testing.T) (e *httpexpect.Expect) { + e = httpexpect.New(t2, t.svr.URL) + res := e.POST("/login").WithJSON(map[string]string{ + "username": t.TestUsername, + "password": t.TestPassword, + }).Expect().JSON().Object() + t.TestToken = res.Path("$.data").String().Raw() + require.NotEmpty(t2, t.TestToken) + return e +} + +func (t *Test) WithAuth(req *httpexpect.Request) *httpexpect.Request { + return req.WithHeader("Authorization", t.TestToken) +} + +var T *Test + +func NewTest() (res *Test, err error) { + // test + t := &Test{} + + // gin app + t.app = gin.New() + + // http test server + t.svr = httptest.NewServer(t.app) + + // init controllers + if err := controllers.InitControllers(); err != nil { + return nil, err + } + + // init routes + if err := routes.InitRoutes(t.app); err != nil { + return nil, err + } + + // dependency injection + c := dig.New() + if err := c.Provide(service.NewService); err != nil { + return nil, trace.TraceError(err) + } + if err := c.Invoke(func(modelSvc service.ModelService) { + t.modelSvc = modelSvc + }); err != nil { + return nil, trace.TraceError(err) + } + + // test data + t.TestUsername = "admin" + t.TestPassword = "admin" + + return t, nil +} diff --git a/core/controllers/test/export_test.go b/core/controllers/test/export_test.go new file mode 100644 index 00000000..6c11e913 --- /dev/null +++ b/core/controllers/test/export_test.go @@ -0,0 +1,59 @@ +package test + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "net/http" + "testing" + "time" +) + +func init() { + viper.Set("mongo.db", "crawlab_test") +} + +func TestExportController_Csv(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + // mongo collection + colName := "test_collection_for_export" + col := mongo.GetMongoCol(colName) + + // insert test data to mongo collection + for i := 0; i < 10; i++ { + _, err := col.Insert(bson.M{ + "field1": i + 1, + "field2": i + 2, + "field3": i + 3, + "field4": i + 4, + }) + require.Nil(t, err) + } + + // export from mongo collection + res := T.WithAuth(e.POST("/export/csv")). + WithQuery("target", colName). + Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data").NotNull() + + // export id + exportId := res.Path("$.data").String().Raw() + + // poll export with export id + for i := 0; i < 10; i++ { + res = T.WithAuth(e.GET("/export/csv/" + exportId)).Expect().Status(http.StatusOK).JSON().Object() + status := res.Path("$.data.status").String().Raw() + if status == constants.TaskStatusFinished { + break + } + time.Sleep(1 * time.Second) + } + + // download exported csv file + csvFileBody := T.WithAuth(e.GET("/export/csv/" + exportId + "/download")).Expect().Status(http.StatusOK).Body() + csvFileBody.NotEmpty() +} diff --git a/core/controllers/test/filter_test.go b/core/controllers/test/filter_test.go new file mode 100644 index 00000000..466d93b1 --- /dev/null +++ b/core/controllers/test/filter_test.go @@ -0,0 +1,76 @@ +package test + +import ( + "encoding/json" + "fmt" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" + "testing" +) + +func init() { + viper.Set("mongo.db", "crawlab_test") +} + +func TestFilterController_GetColFieldOptions(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + // mongo collection + colName := "test_collection_for_filter" + field1 := "field1" + field2 := "field2" + value1 := "value1" + col := mongo.GetMongoCol(colName) + n := 10 + var ids []primitive.ObjectID + var names []string + for i := 0; i < n; i++ { + _id := primitive.NewObjectID() + ids = append(ids, _id) + name := fmt.Sprintf("name_%d", i) + names = append(names, name) + _, err := col.Insert(bson.M{field1: value1, field2: i % 2, "name": name, "_id": _id}) + require.Nil(t, err) + } + + // validate filter options field 1 + res := T.WithAuth(e.GET(fmt.Sprintf("/filters/%s/%s/%s", colName, field1, field1))). + Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data").NotNull() + res.Path("$.data").Array().Length().Equal(1) + res.Path("$.data").Array().Element(0).Path("$.value").Equal(value1) + + // validate filter options field 2 + res = T.WithAuth(e.GET(fmt.Sprintf("/filters/%s/%s/%s", colName, field2, field2))). + Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data").NotNull() + res.Path("$.data").Array().Length().Equal(2) + + // validate filter options with query + conditions := []entity.Condition{{field2, constants.FilterOpEqual, 0}} + conditionsJson, err := json.Marshal(conditions) + conditionsJsonStr := string(conditionsJson) + require.Nil(t, err) + res = T.WithAuth(e.GET(fmt.Sprintf("/filters/%s/%s/%s", colName, field2, field2))). + WithQuery(constants.FilterQueryFieldConditions, conditionsJsonStr). + Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data").NotNull() + res.Path("$.data").Array().Length().Equal(1) + + // validate filter options (basic path) + res = T.WithAuth(e.GET(fmt.Sprintf("/filters/%s", colName))). + Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data").NotNull() + res.Path("$.data").Array().Length().Equal(n) + for i := 0; i < n; i++ { + res.Path("$.data").Array().Element(i).Object().Value("value").Equal(ids[i]) + res.Path("$.data").Array().Element(i).Object().Value("label").Equal(names[i]) + } +} diff --git a/core/controllers/test/main_test.go b/core/controllers/test/main_test.go new file mode 100644 index 00000000..2b9bb76b --- /dev/null +++ b/core/controllers/test/main_test.go @@ -0,0 +1,44 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/user" + "go.mongodb.org/mongo-driver/mongo" + "testing" +) + +func TestMain(m *testing.M) { + // init user + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + _, err = modelSvc.GetUserByUsername(constants.DefaultAdminUsername, nil) + if err != nil { + if err.Error() != mongo.ErrNoDocuments.Error() { + panic(err) + } + userSvc, err := user.GetUserService() + if err != nil { + panic(err) + } + if err := userSvc.Create(&interfaces.UserCreateOptions{ + Username: constants.DefaultAdminUsername, + Password: constants.DefaultAdminPassword, + Role: constants.RoleAdmin, + }); err != nil { + panic(err) + } + } + + if err := controllers.InitControllers(); err != nil { + panic(err) + } + + m.Run() + + T.Cleanup() +} diff --git a/core/controllers/test/project_test.go b/core/controllers/test/project_test.go new file mode 100644 index 00000000..b0a1b2c6 --- /dev/null +++ b/core/controllers/test/project_test.go @@ -0,0 +1,305 @@ +package test + +import ( + "encoding/json" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" + "testing" + "time" +) + +func TestProjectController_Get(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + p := models.Project{ + Name: "test project", + } + res := T.WithAuth(e.POST("/projects")).WithJSON(p).Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data._id").NotNull() + id := res.Path("$.data._id").String().Raw() + oid, err := primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + + res = T.WithAuth(e.GET("/projects/" + id)).WithJSON(p).Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data._id").NotNull() + res.Path("$.data.name").Equal("test project") +} + +func TestProjectController_Put(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + p := models.Project{ + Name: "old name", + Description: "old description", + } + + // add + res := T.WithAuth(e.POST("/projects")). + WithJSON(p). + Expect().Status(http.StatusOK). + JSON().Object() + res.Path("$.data._id").NotNull() + id := res.Path("$.data._id").String().Raw() + oid, err := primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + + // change object + p.Id = oid + p.Name = "new name" + p.Description = "new description" + + // update + T.WithAuth(e.PUT("/projects/" + id)). + WithJSON(p). + Expect().Status(http.StatusOK) + + // check + res = T.WithAuth(e.GET("/projects/" + id)).Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data._id").Equal(id) + res.Path("$.data.name").Equal("new name") + res.Path("$.data.description").Equal("new description") +} + +func TestProjectController_Post(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + p := models.Project{ + Name: "test project", + Description: "this is a test project", + } + + res := T.WithAuth(e.POST("/projects")).WithJSON(p).Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data._id").NotNull() + res.Path("$.data.name").Equal("test project") + res.Path("$.data.description").Equal("this is a test project") +} + +func TestProjectController_Delete(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + p := models.Project{ + Name: "test project", + Description: "this is a test project", + } + + // add + res := T.WithAuth(e.POST("/projects")). + WithJSON(p). + Expect().Status(http.StatusOK). + JSON().Object() + res.Path("$.data._id").NotNull() + id := res.Path("$.data._id").String().Raw() + oid, err := primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + + // get + res = T.WithAuth(e.GET("/projects/" + id)). + Expect().Status(http.StatusOK). + JSON().Object() + res.Path("$.data._id").NotNull() + id = res.Path("$.data._id").String().Raw() + oid, err = primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + + // delete + T.WithAuth(e.DELETE("/projects/" + id)). + Expect().Status(http.StatusOK). + JSON().Object() + + // get + T.WithAuth(e.GET("/projects/" + id)). + Expect().Status(http.StatusNotFound) +} + +func TestProjectController_GetList(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + n := 100 // total + bn := 10 // batch + + for i := 0; i < n; i++ { + p := models.Project{ + Name: fmt.Sprintf("test name %d", i+1), + } + obj := T.WithAuth(e.POST("/projects")).WithJSON(p).Expect().Status(http.StatusOK).JSON().Object() + obj.Path("$.data._id").NotNull() + } + + f := entity.Filter{ + //IsOr: false, + Conditions: []*entity.Condition{ + {Key: "name", Op: constants.FilterOpContains, Value: "test name"}, + }, + } + condBytes, err := json.Marshal(&f.Conditions) + require.Nil(t, err) + + pagination := entity.Pagination{ + Page: 1, + Size: bn, + } + + // get list with pagination + res := T.WithAuth(e.GET("/projects")). + WithQuery("conditions", string(condBytes)). + WithQueryObject(pagination). + Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data").Array().Length().Equal(bn) + res.Path("$.total").Number().Equal(n) + + data := res.Path("$.data").Array() + for i := 0; i < bn; i++ { + obj := data.Element(i) + obj.Path("$.name").Equal(fmt.Sprintf("test name %d", i+1)) + } + +} + +func TestProjectController_PostList(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + n := 10 + var docs []models.Project + for i := 0; i < n; i++ { + docs = append(docs, models.Project{ + Name: fmt.Sprintf("project %d", i+1), + Description: "this is a project", + }) + } + + T.WithAuth(e.POST("/projects/batch")).WithJSON(docs).Expect().Status(http.StatusOK) + + res := T.WithAuth(e.GET("/projects")). + WithQueryObject(entity.Pagination{Page: 1, Size: 10}). + Expect().Status(http.StatusOK). + JSON().Object() + res.Path("$.data").Array().Length().Equal(n) +} + +func TestProjectController_DeleteList(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + n := 10 + var docs []models.Project + for i := 0; i < n; i++ { + docs = append(docs, models.Project{ + Name: fmt.Sprintf("project %d", i+1), + Description: "this is a project", + }) + } + + // add + res := T.WithAuth(e.POST("/projects/batch")).WithJSON(docs).Expect().Status(http.StatusOK). + JSON().Object() + var ids []primitive.ObjectID + data := res.Path("$.data").Array() + for i := 0; i < n; i++ { + obj := data.Element(i) + id := obj.Path("$._id").String().Raw() + oid, err := primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + ids = append(ids, oid) + } + + // delete + payload := entity.BatchRequestPayload{ + Ids: ids, + } + T.WithAuth(e.DELETE("/projects")). + WithJSON(payload). + Expect().Status(http.StatusOK) + + // check + for _, id := range ids { + T.WithAuth(e.GET("/projects/" + id.Hex())). + Expect().Status(http.StatusNotFound) + } + +} + +func TestProjectController_PutList(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + // now + now := time.Now() + + n := 10 + var docs []models.Project + for i := 0; i < n; i++ { + docs = append(docs, models.Project{ + Name: "old name", + Description: "old description", + }) + } + + // add + res := T.WithAuth(e.POST("/projects/batch")).WithJSON(docs).Expect().Status(http.StatusOK). + JSON().Object() + var ids []primitive.ObjectID + data := res.Path("$.data").Array() + for i := 0; i < n; i++ { + obj := data.Element(i) + id := obj.Path("$._id").String().Raw() + oid, err := primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + ids = append(ids, oid) + } + + // wait for 100 millisecond + time.Sleep(100 * time.Millisecond) + + // update + p := models.Project{ + Name: "new name", + Description: "new description", + } + dataBytes, err := json.Marshal(&p) + require.Nil(t, err) + payload := entity.BatchRequestPayloadWithStringData{ + Ids: ids, + Data: string(dataBytes), + Fields: []string{ + "name", + "description", + }, + } + T.WithAuth(e.PUT("/projects")).WithJSON(payload).Expect().Status(http.StatusOK) + + // check response data + for i := 0; i < n; i++ { + res = T.WithAuth(e.GET("/projects/" + ids[i].Hex())).Expect().Status(http.StatusOK).JSON().Object() + res.Path("$.data.name").Equal("new name") + res.Path("$.data.description").Equal("new description") + } + + // check artifacts + pl, err := T.modelSvc.GetProjectList(bson.M{"_id": bson.M{"$in": ids}}, nil) + require.Nil(t, err) + for _, p := range pl { + a, err := delegate.NewModelDelegate(&p).GetArtifact() + require.Nil(t, err) + require.True(t, a.GetSys().GetUpdateTs().After(now)) + require.True(t, a.GetSys().GetUpdateTs().After(a.GetSys().GetCreateTs())) + } +} diff --git a/core/controllers/test/spider_test.go b/core/controllers/test/spider_test.go new file mode 100644 index 00000000..b8c4dc06 --- /dev/null +++ b/core/controllers/test/spider_test.go @@ -0,0 +1,183 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" + "testing" +) + +func TestSpiderController_Delete(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + s := models.Spider{ + Name: "test spider", + Description: "this is a test spider", + ColName: "test col name", + } + + // add spider + res := T.WithAuth(e.POST("/spiders")). + WithJSON(s). + Expect().Status(http.StatusOK). + JSON().Object() + res.Path("$.data._id").NotNull() + id := res.Path("$.data._id").String().Raw() + oid, err := primitive.ObjectIDFromHex(id) + require.Nil(t, err) + require.False(t, oid.IsZero()) + + // add tasks + var taskIds []primitive.ObjectID + tasks := []models.Task{ + { + Id: primitive.NewObjectID(), + SpiderId: oid, + }, + { + Id: primitive.NewObjectID(), + SpiderId: oid, + }, + } + for _, task := range tasks { + // add task + err := delegate.NewModelDelegate(&task).Add() + require.Nil(t, err) + + // add task stat + err = delegate.NewModelDelegate(&models.TaskStat{ + Id: task.Id, + }).Add() + require.Nil(t, err) + + taskIds = append(taskIds, task.Id) + } + + // delete + T.WithAuth(e.DELETE("/spiders/" + id)). + Expect().Status(http.StatusOK) + + // get + T.WithAuth(e.GET("/spiders/" + id)). + Expect().Status(http.StatusNotFound) + + // get tasks + for _, task := range tasks { + T.WithAuth(e.GET("/tasks/" + task.Id.Hex())). + Expect().Status(http.StatusNotFound) + } + + // spider stat + modelSpiderStatSvc := service.NewBaseService(interfaces.ModelIdSpiderStat) + spiderStatCount, err := modelSpiderStatSvc.Count(bson.M{ + "_id": oid, + }) + require.Nil(t, err) + require.Zero(t, spiderStatCount) + + // task stats + modelTaskStatSvc := service.NewBaseService(interfaces.ModelIdTaskStat) + taskStatCount, err := modelTaskStatSvc.Count(bson.M{ + "_id": bson.M{ + "$in": taskIds, + }, + }) + require.Nil(t, err) + require.Zero(t, taskStatCount) +} + +func TestSpiderController_DeleteList(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + spiders := []models.Spider{ + { + Id: primitive.NewObjectID(), + Name: "test spider 1", + Description: "this is a test spider 1", + ColName: "test col name 1", + }, + { + Id: primitive.NewObjectID(), + Name: "test spider 2", + Description: "this is a test spider 2", + ColName: "test col name 2", + }, + } + + // add spiders + for _, spider := range spiders { + T.WithAuth(e.POST("/spiders")). + WithJSON(spider). + Expect().Status(http.StatusOK) + } + + var spiderIds []primitive.ObjectID + var taskIds []primitive.ObjectID + for _, spider := range spiders { + // task id + taskId := primitive.NewObjectID() + + // add task + err := delegate.NewModelDelegate(&models.Task{ + Id: taskId, + SpiderId: spider.Id, + }).Add() + require.Nil(t, err) + + // add task stats + err = delegate.NewModelDelegate(&models.TaskStat{ + Id: taskId, + }).Add() + require.Nil(t, err) + + spiderIds = append(spiderIds, spider.Id) + taskIds = append(taskIds, taskId) + } + + // delete spiders + T.WithAuth(e.DELETE("/spiders")). + WithJSON(entity.BatchRequestPayload{ + Ids: spiderIds, + }).Expect().Status(http.StatusOK) + + // get spiders + for _, spider := range spiders { + // get + T.WithAuth(e.GET("/spiders/" + spider.Id.Hex())). + Expect().Status(http.StatusNotFound) + } + + // get tasks + for _, taskId := range taskIds { + T.WithAuth(e.GET("/tasks/" + taskId.Hex())). + Expect().Status(http.StatusNotFound) + } + + // spider stat + modelSpiderStatSvc := service.NewBaseService(interfaces.ModelIdSpiderStat) + spiderStatCount, err := modelSpiderStatSvc.Count(bson.M{ + "_id": bson.M{ + "$in": spiderIds, + }, + }) + require.Nil(t, err) + require.Zero(t, spiderStatCount) + + // task stats + modelTaskStatSvc := service.NewBaseService(interfaces.ModelIdTaskStat) + taskStatCount, err := modelTaskStatSvc.Count(bson.M{ + "_id": bson.M{ + "$in": taskIds, + }, + }) + require.Nil(t, err) + require.Zero(t, taskStatCount) +} diff --git a/core/controllers/test/task_test.go b/core/controllers/test/task_test.go new file mode 100644 index 00000000..4d81738b --- /dev/null +++ b/core/controllers/test/task_test.go @@ -0,0 +1,102 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" + "testing" +) + +func TestTaskController_Delete(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + task := models.Task{ + Id: primitive.NewObjectID(), + } + + // add task + err := delegate.NewModelDelegate(&task).Add() + require.Nil(t, err) + + // add task stat + err = delegate.NewModelDelegate(&models.TaskStat{ + Id: task.Id, + }).Add() + require.Nil(t, err) + + // delete + T.WithAuth(e.DELETE("/tasks/" + task.Id.Hex())). + Expect().Status(http.StatusOK) + + // get + T.WithAuth(e.GET("/tasks/" + task.Id.Hex())). + Expect().Status(http.StatusNotFound) + + // task stats + modelTaskStatSvc := service.NewBaseService(interfaces.ModelIdTaskStat) + taskStatCount, err := modelTaskStatSvc.Count(bson.M{ + "_id": task.Id, + }) + require.Nil(t, err) + require.Zero(t, taskStatCount) +} + +func TestTaskController_DeleteList(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + + tasks := []models.Task{ + { + Id: primitive.NewObjectID(), + }, + { + Id: primitive.NewObjectID(), + }, + } + + // add spiders + var taskIds []primitive.ObjectID + for _, task := range tasks { + // add task + err := delegate.NewModelDelegate(&task).Add() + require.Nil(t, err) + + // add task stat + err = delegate.NewModelDelegate(&models.TaskStat{ + Id: task.Id, + }).Add() + require.Nil(t, err) + + taskIds = append(taskIds, task.Id) + } + + // delete tasks + T.WithAuth(e.DELETE("/tasks")). + WithJSON(entity.BatchRequestPayload{ + Ids: taskIds, + }).Expect().Status(http.StatusOK) + + // get tasks + for _, task := range tasks { + // get + T.WithAuth(e.GET("/tasks/" + task.Id.Hex())). + Expect().Status(http.StatusNotFound) + } + + // task stats + modelTaskStatSvc := service.NewBaseService(interfaces.ModelIdTaskStat) + taskStatCount, err := modelTaskStatSvc.Count(bson.M{ + "_id": bson.M{ + "$in": taskIds, + }, + }) + require.Nil(t, err) + require.Zero(t, taskStatCount) +} diff --git a/core/controllers/token.go b/core/controllers/token.go new file mode 100644 index 00000000..f94f3b68 --- /dev/null +++ b/core/controllers/token.go @@ -0,0 +1,84 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" +) + +var TokenController *tokenController + +var TokenActions []Action + +type tokenController struct { + ListActionControllerDelegate + d ListActionControllerDelegate + ctx *tokenContext +} + +func (ctr *tokenController) Post(c *gin.Context) { + var err error + var t models.Token + if err := c.ShouldBindJSON(&t); err != nil { + HandleErrorBadRequest(c, err) + return + } + u, err := ctr.ctx.userSvc.GetCurrentUser(c) + if err != nil { + HandleErrorUnauthorized(c, err) + return + } + t.Token, err = ctr.ctx.userSvc.MakeToken(u) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + if err := delegate.NewModelDelegate(&t, GetUserFromContext(c)).Add(); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +type tokenContext struct { + modelSvc service.ModelService + userSvc interfaces.UserService +} + +func newTokenContext() *tokenContext { + // context + ctx := &tokenContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + userSvc interfaces.UserService, + ) { + ctx.modelSvc = modelSvc + ctx.userSvc = userSvc + }); err != nil { + panic(err) + } + + return ctx +} + +func newTokenController() *tokenController { + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListPostActionControllerDelegate(ControllerIdToken, modelSvc.GetBaseService(interfaces.ModelIdToken), TokenActions) + d := NewListPostActionControllerDelegate(ControllerIdToken, modelSvc.GetBaseService(interfaces.ModelIdToken), TokenActions) + ctx := newTokenContext() + + return &tokenController{ + ListActionControllerDelegate: *ctr, + d: *d, + ctx: ctx, + } +} diff --git a/core/controllers/token_v2.go b/core/controllers/token_v2.go new file mode 100644 index 00000000..bac880b2 --- /dev/null +++ b/core/controllers/token_v2.go @@ -0,0 +1,35 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/user" + "github.com/gin-gonic/gin" +) + +func PostToken(c *gin.Context) { + var t models.TokenV2 + if err := c.ShouldBindJSON(&t); err != nil { + HandleErrorBadRequest(c, err) + return + } + svc, err := user.GetUserServiceV2() + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + u := GetUserFromContextV2(c) + t.SetCreated(u.Id) + t.SetUpdated(u.Id) + t.Token, err = svc.MakeToken(u) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + _, err = service.NewModelServiceV2[models.TokenV2]().InsertOne(t) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} diff --git a/core/controllers/user.go b/core/controllers/user.go new file mode 100644 index 00000000..e243b02c --- /dev/null +++ b/core/controllers/user.go @@ -0,0 +1,244 @@ +package controllers + +import ( + "encoding/json" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + delegate2 "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "net/http" +) + +var UserController *userController + +func getUserActions() []Action { + userCtx := newUserContext() + return []Action{ + { + Method: http.MethodPost, + Path: "/:id/change-password", + HandlerFunc: userCtx.changePassword, + }, + { + Method: http.MethodGet, + Path: "/me", + HandlerFunc: userCtx.getMe, + }, + { + Method: http.MethodPut, + Path: "/me", + HandlerFunc: userCtx.putMe, + }, + } +} + +type userController struct { + ListActionControllerDelegate + d ListActionControllerDelegate + ctx *userContext +} + +func (ctr *userController) Post(c *gin.Context) { + var u models.User + if err := c.ShouldBindJSON(&u); err != nil { + HandleErrorBadRequest(c, err) + return + } + if err := ctr.ctx.userSvc.Create(&interfaces.UserCreateOptions{ + Username: u.Username, + Password: u.Password, + Email: u.Email, + Role: u.Role, + }); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctr *userController) PostList(c *gin.Context) { + // users + var users []models.User + if err := c.ShouldBindJSON(&users); err != nil { + HandleErrorBadRequest(c, err) + return + } + + for _, u := range users { + if err := ctr.ctx.userSvc.Create(&interfaces.UserCreateOptions{ + Username: u.Username, + Password: u.Password, + Email: u.Email, + Role: u.Role, + }); err != nil { + trace.PrintError(err) + } + } + + HandleSuccess(c) +} + +func (ctr *userController) PutList(c *gin.Context) { + // payload + var payload entity.BatchRequestPayloadWithStringData + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // doc to update + var doc models.User + if err := json.Unmarshal([]byte(payload.Data), &doc); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // query + query := bson.M{ + "_id": bson.M{ + "$in": payload.Ids, + }, + } + + // update users + if err := ctr.ctx.modelSvc.GetBaseService(interfaces.ModelIdUser).UpdateDoc(query, &doc, payload.Fields); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // update passwords + if utils.Contains(payload.Fields, "password") { + for _, id := range payload.Ids { + if err := ctr.ctx.userSvc.ChangePassword(id, doc.Password); err != nil { + trace.PrintError(err) + } + } + } + + HandleSuccess(c) +} + +type userContext struct { + modelSvc service.ModelService + userSvc interfaces.UserService +} + +func (ctx *userContext) changePassword(c *gin.Context) { + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + var payload map[string]string + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + password, ok := payload["password"] + if !ok { + HandleErrorBadRequest(c, errors.ErrorUserMissingRequiredFields) + return + } + if len(password) < 5 { + HandleErrorBadRequest(c, errors.ErrorUserInvalidPassword) + return + } + if err := ctx.userSvc.ChangePassword(id, password); err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccess(c) +} + +func (ctx *userContext) getMe(c *gin.Context) { + u, err := ctx._getMe(c) + if err != nil { + HandleErrorUnauthorized(c, errors.ErrorUserUnauthorized) + return + } + HandleSuccessWithData(c, u) +} + +func (ctx *userContext) putMe(c *gin.Context) { + // current user + u, err := ctx._getMe(c) + if err != nil { + HandleErrorUnauthorized(c, errors.ErrorUserUnauthorized) + return + } + + // payload + doc, err := NewJsonBinder(ControllerIdUser).Bind(c) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + if doc.GetId() != u.GetId() { + HandleErrorBadRequest(c, errors.ErrorHttpBadRequest) + return + } + + // save to db + if err := delegate2.NewModelDelegate(doc, GetUserFromContext(c)).Save(); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, doc) +} + +func (ctx *userContext) _getMe(c *gin.Context) (u interfaces.User, err error) { + res, ok := c.Get(constants.UserContextKey) + if !ok { + return nil, trace.TraceError(errors.ErrorUserNotExistsInContext) + } + u, ok = res.(interfaces.User) + if !ok { + return nil, trace.TraceError(errors.ErrorUserInvalidType) + } + return u, nil +} + +func newUserContext() *userContext { + // context + ctx := &userContext{} + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + userSvc interfaces.UserService, + ) { + ctx.modelSvc = modelSvc + ctx.userSvc = userSvc + }); err != nil { + panic(err) + } + + return ctx +} + +func newUserController() *userController { + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + + ctr := NewListPostActionControllerDelegate(ControllerIdUser, modelSvc.GetBaseService(interfaces.ModelIdUser), getUserActions()) + d := NewListPostActionControllerDelegate(ControllerIdUser, modelSvc.GetBaseService(interfaces.ModelIdUser), getUserActions()) + ctx := newUserContext() + + return &userController{ + ListActionControllerDelegate: *ctr, + d: *d, + ctx: ctx, + } +} diff --git a/core/controllers/user_v2.go b/core/controllers/user_v2.go new file mode 100644 index 00000000..434b2061 --- /dev/null +++ b/core/controllers/user_v2.go @@ -0,0 +1,126 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func PostUser(c *gin.Context) { + var payload struct { + Username string `json:"username"` + Password string `json:"password"` + Role string `json:"role"` + Email string `json:"email"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + u := GetUserFromContextV2(c) + model := models.UserV2{ + Username: payload.Username, + Password: utils.EncryptMd5(payload.Password), + Role: payload.Role, + Email: payload.Email, + } + model.SetCreated(u.Id) + model.SetUpdated(u.Id) + id, err := service.NewModelServiceV2[models.UserV2]().InsertOne(model) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + result, err := service.NewModelServiceV2[models.UserV2]().GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + + HandleSuccessWithData(c, result) + +} + +func PostUserChangePassword(c *gin.Context) { + // get id + id, err := primitive.ObjectIDFromHex(c.Param("id")) + if err != nil { + HandleErrorBadRequest(c, err) + return + } + + // get payload + var payload struct { + Password string `json:"password"` + } + if err := c.ShouldBindJSON(&payload); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // get user + u := GetUserFromContextV2(c) + modelSvc := service.NewModelServiceV2[models.UserV2]() + + // update password + user, err := modelSvc.GetById(id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + user.SetUpdated(u.Id) + user.Password = utils.EncryptMd5(payload.Password) + if err := modelSvc.ReplaceById(user.Id, *user); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // handle success + HandleSuccess(c) +} + +func GetUserMe(c *gin.Context) { + u := GetUserFromContextV2(c) + _u, err := service.NewModelServiceV2[models.UserV2]().GetById(u.Id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + HandleSuccessWithData(c, _u) +} + +func PutUserById(c *gin.Context) { + // get payload + var user models.UserV2 + if err := c.ShouldBindJSON(&user); err != nil { + HandleErrorBadRequest(c, err) + return + } + + // get user + u := GetUserFromContextV2(c) + + modelSvc := service.NewModelServiceV2[models.UserV2]() + + // update user + userDb, err := modelSvc.GetById(u.Id) + if err != nil { + HandleErrorInternalServerError(c, err) + return + } + user.Password = userDb.Password + user.SetUpdated(u.Id) + if user.Id.IsZero() { + user.Id = u.Id + } + if err := modelSvc.ReplaceById(u.Id, user); err != nil { + HandleErrorInternalServerError(c, err) + return + } + + // handle success + HandleSuccess(c) +} diff --git a/core/controllers/user_v2_test.go b/core/controllers/user_v2_test.go new file mode 100644 index 00000000..c86739fc --- /dev/null +++ b/core/controllers/user_v2_test.go @@ -0,0 +1,89 @@ +package controllers_test + +import ( + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/crawlab-team/crawlab/core/middlewares" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestPostUserChangePassword_Success(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + modelSvc := service.NewModelServiceV2[models.UserV2]() + u := models.UserV2{} + id, err := modelSvc.InsertOne(u) + assert.Nil(t, err) + u.SetId(id) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.POST("/users/:id/change-password", controllers.PostUserChangePassword) + + password := "newPassword" + reqBody := strings.NewReader(`{"password":"` + password + `"}`) + req, _ := http.NewRequest(http.MethodPost, "/users/"+id.Hex()+"/change-password", reqBody) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", TestToken) + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestGetUserMe_Success(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + modelSvc := service.NewModelServiceV2[models.UserV2]() + u := models.UserV2{} + id, err := modelSvc.InsertOne(u) + assert.Nil(t, err) + u.SetId(id) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.GET("/users/me", controllers.GetUserMe) + + req, _ := http.NewRequest(http.MethodGet, "/users/me", nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", TestToken) + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} + +func TestPutUserById_Success(t *testing.T) { + SetupTestDB() + defer CleanupTestDB() + + modelSvc := service.NewModelServiceV2[models.UserV2]() + u := models.UserV2{} + id, err := modelSvc.InsertOne(u) + assert.Nil(t, err) + u.SetId(id) + + router := gin.Default() + router.Use(middlewares.AuthorizationMiddlewareV2()) + router.PUT("/users/me", controllers.PutUserById) + + reqBody := strings.NewReader(`{"id":"` + id.Hex() + `","username":"newUsername","email":"newEmail@test.com"}`) + req, _ := http.NewRequest(http.MethodPut, "/users/me", reqBody) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", TestToken) + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) +} diff --git a/core/controllers/utils_context.go b/core/controllers/utils_context.go new file mode 100644 index 00000000..9365bd0d --- /dev/null +++ b/core/controllers/utils_context.go @@ -0,0 +1,32 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/gin-gonic/gin" +) + +func GetUserFromContext(c *gin.Context) (u interfaces.User) { + value, ok := c.Get(constants.UserContextKey) + if !ok { + return nil + } + u, ok = value.(interfaces.User) + if !ok { + return nil + } + return u +} + +func GetUserFromContextV2(c *gin.Context) (u *models.UserV2) { + value, ok := c.Get(constants.UserContextKey) + if !ok { + return nil + } + u, ok = value.(*models.UserV2) + if !ok { + return nil + } + return u +} diff --git a/core/controllers/utils_filter.go b/core/controllers/utils_filter.go new file mode 100644 index 00000000..9a7b619c --- /dev/null +++ b/core/controllers/utils_filter.go @@ -0,0 +1,123 @@ +package controllers + +import ( + "encoding/json" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "reflect" + "strings" +) + +// GetFilter Get entity.Filter from gin.Context +func GetFilter(c *gin.Context) (f *entity.Filter, err error) { + // bind + condStr := c.Query(constants.FilterQueryFieldConditions) + var conditions []*entity.Condition + if err := json.Unmarshal([]byte(condStr), &conditions); err != nil { + return nil, err + } + + // attempt to convert object id + for i, cond := range conditions { + v := reflect.ValueOf(cond.Value) + switch v.Kind() { + case reflect.String: + item := cond.Value.(string) + id, err := primitive.ObjectIDFromHex(item) + if err == nil { + conditions[i].Value = id + } else { + conditions[i].Value = item + } + case reflect.Slice, reflect.Array: + var items []interface{} + for i := 0; i < v.Len(); i++ { + vItem := v.Index(i) + item := vItem.Interface() + + // string + stringItem, ok := item.(string) + if ok { + id, err := primitive.ObjectIDFromHex(stringItem) + if err == nil { + items = append(items, id) + } else { + items = append(items, stringItem) + } + continue + } + + // default + items = append(items, item) + } + conditions[i].Value = items + } + } + + return &entity.Filter{ + IsOr: false, + Conditions: conditions, + }, nil +} + +// GetFilterQuery Get bson.M from gin.Context +func GetFilterQuery(c *gin.Context) (q bson.M, err error) { + f, err := GetFilter(c) + if err != nil { + return nil, err + } + + if f == nil { + return nil, nil + } + + // TODO: implement logic OR + + return utils.FilterToQuery(f) +} + +func MustGetFilterQuery(c *gin.Context) (q bson.M) { + q, err := GetFilterQuery(c) + if err != nil { + return nil + } + return q +} + +// GetFilterAll Get all from gin.Context +func GetFilterAll(c *gin.Context) (res bool, err error) { + resStr := c.Query(constants.FilterQueryFieldAll) + switch strings.ToUpper(resStr) { + case "1": + return true, nil + case "0": + return false, nil + case "Y": + return true, nil + case "N": + return false, nil + case "T": + return true, nil + case "F": + return false, nil + case "TRUE": + return true, nil + case "FALSE": + return false, nil + default: + return false, errors.ErrorFilterInvalidOperation + } +} + +func MustGetFilterAll(c *gin.Context) (res bool) { + res, err := GetFilterAll(c) + if err != nil { + return false + } + return res +} diff --git a/core/controllers/utils_http.go b/core/controllers/utils_http.go new file mode 100644 index 00000000..60ce95eb --- /dev/null +++ b/core/controllers/utils_http.go @@ -0,0 +1,72 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "net/http" +) + +func handleError(statusCode int, c *gin.Context, err error, print bool) { + if print { + trace.PrintError(err) + } + c.AbortWithStatusJSON(statusCode, entity.Response{ + Status: constants.HttpResponseStatusOk, + Message: constants.HttpResponseMessageError, + Error: err.Error(), + }) +} + +func HandleError(statusCode int, c *gin.Context, err error) { + handleError(statusCode, c, err, true) +} + +func HandleErrorNoPrint(statusCode int, c *gin.Context, err error) { + handleError(statusCode, c, err, false) +} + +func HandleErrorBadRequest(c *gin.Context, err error) { + HandleError(http.StatusBadRequest, c, err) +} + +func HandleErrorUnauthorized(c *gin.Context, err error) { + HandleError(http.StatusUnauthorized, c, err) +} + +func HandleErrorNotFound(c *gin.Context, err error) { + HandleError(http.StatusNotFound, c, err) +} + +func HandleErrorNotFoundNoPrint(c *gin.Context, err error) { + HandleErrorNoPrint(http.StatusNotFound, c, err) +} + +func HandleErrorInternalServerError(c *gin.Context, err error) { + HandleError(http.StatusInternalServerError, c, err) +} + +func HandleSuccess(c *gin.Context) { + c.AbortWithStatusJSON(http.StatusOK, entity.Response{ + Status: constants.HttpResponseStatusOk, + Message: constants.HttpResponseMessageSuccess, + }) +} + +func HandleSuccessWithData(c *gin.Context, data interface{}) { + c.AbortWithStatusJSON(http.StatusOK, entity.Response{ + Status: constants.HttpResponseStatusOk, + Message: constants.HttpResponseMessageSuccess, + Data: data, + }) +} + +func HandleSuccessWithListData(c *gin.Context, data interface{}, total int) { + c.AbortWithStatusJSON(http.StatusOK, entity.ListResponse{ + Status: constants.HttpResponseStatusOk, + Message: constants.HttpResponseMessageSuccess, + Data: data, + Total: total, + }) +} diff --git a/core/controllers/utils_pagination.go b/core/controllers/utils_pagination.go new file mode 100644 index 00000000..d16278af --- /dev/null +++ b/core/controllers/utils_pagination.go @@ -0,0 +1,30 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/gin-gonic/gin" +) + +func GetDefaultPagination() (p *entity.Pagination) { + return &entity.Pagination{ + Page: constants.PaginationDefaultPage, + Size: constants.PaginationDefaultSize, + } +} + +func GetPagination(c *gin.Context) (p *entity.Pagination, err error) { + var _p entity.Pagination + if err := c.ShouldBindQuery(&_p); err != nil { + return GetDefaultPagination(), err + } + return &_p, nil +} + +func MustGetPagination(c *gin.Context) (p *entity.Pagination) { + p, err := GetPagination(c) + if err != nil || p == nil { + return GetDefaultPagination() + } + return p +} diff --git a/core/controllers/utils_sort.go b/core/controllers/utils_sort.go new file mode 100644 index 00000000..86e68e96 --- /dev/null +++ b/core/controllers/utils_sort.go @@ -0,0 +1,58 @@ +package controllers + +import ( + "encoding/json" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" +) + +// GetSorts Get entity.Sort from gin.Context +func GetSorts(c *gin.Context) (sorts []entity.Sort, err error) { + // bind + sortStr := c.Query(constants.SortQueryField) + if err := json.Unmarshal([]byte(sortStr), &sorts); err != nil { + return nil, err + } + return sorts, nil +} + +// GetSortsOption Get entity.Sort from gin.Context +func GetSortsOption(c *gin.Context) (sort bson.D, err error) { + sorts, err := GetSorts(c) + if err != nil { + return nil, err + } + + if sorts == nil || len(sorts) == 0 { + return bson.D{{"_id", -1}}, nil + } + + return SortsToOption(sorts) +} + +func MustGetSortOption(c *gin.Context) (sort bson.D) { + sort, err := GetSortsOption(c) + if err != nil { + return nil + } + return sort +} + +// SortsToOption Translate entity.Sort to bson.D +func SortsToOption(sorts []entity.Sort) (sort bson.D, err error) { + sort = bson.D{} + for _, s := range sorts { + switch s.Direction { + case constants.ASCENDING: + sort = append(sort, bson.E{Key: s.Key, Value: 1}) + case constants.DESCENDING: + sort = append(sort, bson.E{Key: s.Key, Value: -1}) + } + } + if len(sort) == 0 { + sort = bson.D{{"_id", -1}} + } + return sort, nil +} diff --git a/core/controllers/version.go b/core/controllers/version.go new file mode 100644 index 00000000..83e3fe95 --- /dev/null +++ b/core/controllers/version.go @@ -0,0 +1,23 @@ +package controllers + +import ( + "github.com/crawlab-team/crawlab/core/config" + "github.com/gin-gonic/gin" + "net/http" +) + +func GetVersion(c *gin.Context) { + HandleSuccessWithData(c, config.GetVersion()) +} + +func getVersionActions() []Action { + return []Action{ + { + Method: http.MethodGet, + Path: "", + HandlerFunc: GetVersion, + }, + } +} + +var VersionController ActionController diff --git a/core/data/colors.go b/core/data/colors.go new file mode 100644 index 00000000..6c97fb87 --- /dev/null +++ b/core/data/colors.go @@ -0,0 +1,5212 @@ +package data + +const ColorsDataText = `[ + { + "name": "Absolute Zero", + "hex": "#0048BA" + }, + { + "name": "Acid Green", + "hex": "#B0BF1A" + }, + { + "name": "Aero", + "hex": "#7CB9E8" + }, + { + "name": "Aero Blue", + "hex": "#C9FFE5" + }, + { + "name": "African Violet", + "hex": "#B284BE" + }, + { + "name": "Air Force Blue (RAF)", + "hex": "#5D8AA8" + }, + { + "name": "Air Force Blue (USAF)", + "hex": "#00308F" + }, + { + "name": "Air Superiority Blue", + "hex": "#72A0C1" + }, + { + "name": "Alabama Crimson", + "hex": "#AF002A" + }, + { + "name": "Alabaster", + "hex": "#F2F0E6" + }, + { + "name": "Alice Blue", + "hex": "#F0F8FF" + }, + { + "name": "Alien Armpit", + "hex": "#84DE02" + }, + { + "name": "Alizarin Crimson", + "hex": "#E32636" + }, + { + "name": "Alloy Orange", + "hex": "#C46210" + }, + { + "name": "Almond", + "hex": "#EFDECD" + }, + { + "name": "Amaranth", + "hex": "#E52B50" + }, + { + "name": "Amaranth Deep Purple", + "hex": "#9F2B68" + }, + { + "name": "Amaranth Pink", + "hex": "#F19CBB" + }, + { + "name": "Amaranth Purple", + "hex": "#AB274F" + }, + { + "name": "Amaranth Red", + "hex": "#D3212D" + }, + { + "name": "Amazon Store", + "hex": "#3B7A57" + }, + { + "name": "Amazonite", + "hex": "#00C4B0" + }, + { + "name": "Amber", + "hex": "#FFBF00" + }, + { + "name": "Amber (SAE/ECE)", + "hex": "#FF7E00" + }, + { + "name": "American Rose", + "hex": "#FF033E" + }, + { + "name": "Amethyst", + "hex": "#9966CC" + }, + { + "name": "Android Green", + "hex": "#A4C639" + }, + { + "name": "Anti-Flash White", + "hex": "#F2F3F4" + }, + { + "name": "Antique Brass", + "hex": "#CD9575" + }, + { + "name": "Antique Bronze", + "hex": "#665D1E" + }, + { + "name": "Antique Fuchsia", + "hex": "#915C83" + }, + { + "name": "Antique Ruby", + "hex": "#841B2D" + }, + { + "name": "Antique White", + "hex": "#FAEBD7" + }, + { + "name": "Ao (English)", + "hex": "#008000" + }, + { + "name": "Apple Green", + "hex": "#8DB600" + }, + { + "name": "Apricot", + "hex": "#FBCEB1" + }, + { + "name": "Aqua", + "hex": "#00FFFF" + }, + { + "name": "Aquamarine", + "hex": "#7FFFD4" + }, + { + "name": "Arctic Lime", + "hex": "#D0FF14" + }, + { + "name": "Army Green", + "hex": "#4B5320" + }, + { + "name": "Arsenic", + "hex": "#3B444B" + }, + { + "name": "Artichoke", + "hex": "#8F9779" + }, + { + "name": "Arylide Yellow", + "hex": "#E9D66B" + }, + { + "name": "Ash Gray", + "hex": "#B2BEB5" + }, + { + "name": "Asparagus", + "hex": "#87A96B" + }, + { + "name": "Atomic Tangerine", + "hex": "#FF9966" + }, + { + "name": "Auburn", + "hex": "#A52A2A" + }, + { + "name": "Aureolin", + "hex": "#FDEE00" + }, + { + "name": "AuroMetalSaurus", + "hex": "#6E7F80" + }, + { + "name": "Avocado", + "hex": "#568203" + }, + { + "name": "Awesome", + "hex": "#FF2052" + }, + { + "name": "Aztec Gold", + "hex": "#C39953" + }, + { + "name": "Azure", + "hex": "#007FFF" + }, + { + "name": "Azure (Web Color)", + "hex": "#F0FFFF" + }, + { + "name": "Azure Mist", + "hex": "#F0FFFF" + }, + { + "name": "Azureish White", + "hex": "#DBE9F4" + }, + { + "name": "Baby Blue", + "hex": "#89CFF0" + }, + { + "name": "Baby Blue Eyes", + "hex": "#A1CAF1" + }, + { + "name": "Baby Pink", + "hex": "#F4C2C2" + }, + { + "name": "Baby Powder", + "hex": "#FEFEFA" + }, + { + "name": "Baker-Miller Pink", + "hex": "#FF91AF" + }, + { + "name": "Ball Blue", + "hex": "#21ABCD" + }, + { + "name": "Banana Mania", + "hex": "#FAE7B5" + }, + { + "name": "Banana Yellow", + "hex": "#FFE135" + }, + { + "name": "Bangladesh Green", + "hex": "#006A4E" + }, + { + "name": "Barbie Pink", + "hex": "#E0218A" + }, + { + "name": "Barn Red", + "hex": "#7C0A02" + }, + { + "name": "Battery Charged Blue", + "hex": "#1DACD6" + }, + { + "name": "Battleship Grey", + "hex": "#848482" + }, + { + "name": "Bazaar", + "hex": "#98777B" + }, + { + "name": "Beau Blue", + "hex": "#BCD4E6" + }, + { + "name": "Beaver", + "hex": "#9F8170" + }, + { + "name": "Begonia", + "hex": "#FA6E79" + }, + { + "name": "Beige", + "hex": "#F5F5DC" + }, + { + "name": "B'dazzled Blue", + "hex": "#2E5894" + }, + { + "name": "Big Dip O'ruby", + "hex": "#9C2542" + }, + { + "name": "Big Foot Feet", + "hex": "#E88E5A" + }, + { + "name": "Bisque", + "hex": "#FFE4C4" + }, + { + "name": "Bistre", + "hex": "#3D2B1F" + }, + { + "name": "Bistre Brown", + "hex": "#967117" + }, + { + "name": "Bitter Lemon", + "hex": "#CAE00D" + }, + { + "name": "Bitter Lime", + "hex": "#BFFF00" + }, + { + "name": "Bittersweet", + "hex": "#FE6F5E" + }, + { + "name": "Bittersweet Shimmer", + "hex": "#BF4F51" + }, + { + "name": "Black", + "hex": "#000000" + }, + { + "name": "Black Bean", + "hex": "#3D0C02" + }, + { + "name": "Black Coral", + "hex": "#54626F" + }, + { + "name": "Black Leather Jacket", + "hex": "#253529" + }, + { + "name": "Black Olive", + "hex": "#3B3C36" + }, + { + "name": "Black Shadows", + "hex": "#BFAFB2" + }, + { + "name": "Blanched Almond", + "hex": "#FFEBCD" + }, + { + "name": "Blast-Off Bronze", + "hex": "#A57164" + }, + { + "name": "Bleu De France", + "hex": "#318CE7" + }, + { + "name": "Blizzard Blue", + "hex": "#ACE5EE" + }, + { + "name": "Blond", + "hex": "#FAF0BE" + }, + { + "name": "Blue", + "hex": "#0000FF" + }, + { + "name": "Blue (Crayola)", + "hex": "#1F75FE" + }, + { + "name": "Blue (Munsell)", + "hex": "#0093AF" + }, + { + "name": "Blue (NCS)", + "hex": "#0087BD" + }, + { + "name": "Blue (Pantone)", + "hex": "#0018A8" + }, + { + "name": "Blue (Pigment)", + "hex": "#333399" + }, + { + "name": "Blue (RYB)", + "hex": "#0247FE" + }, + { + "name": "Blue Bell", + "hex": "#A2A2D0" + }, + { + "name": "Blue Bolt", + "hex": "#00B9FB" + }, + { + "name": "Blue-Gray", + "hex": "#6699CC" + }, + { + "name": "Blue-Green", + "hex": "#0D98BA" + }, + { + "name": "Blue Jeans", + "hex": "#5DADEC" + }, + { + "name": "Blue Lagoon", + "hex": "#ACE5EE" + }, + { + "name": "Blue-Magenta Violet", + "hex": "#553592" + }, + { + "name": "Blue Sapphire", + "hex": "#126180" + }, + { + "name": "Blue-Violet", + "hex": "#8A2BE2" + }, + { + "name": "Blue Yonder", + "hex": "#5072A7" + }, + { + "name": "Blueberry", + "hex": "#4F86F7" + }, + { + "name": "Bluebonnet", + "hex": "#1C1CF0" + }, + { + "name": "Blush", + "hex": "#DE5D83" + }, + { + "name": "Bole", + "hex": "#79443B" + }, + { + "name": "Bondi Blue", + "hex": "#0095B6" + }, + { + "name": "Bone", + "hex": "#E3DAC9" + }, + { + "name": "Booger Buster", + "hex": "#DDE26A" + }, + { + "name": "Boston University Red", + "hex": "#CC0000" + }, + { + "name": "Bottle Green", + "hex": "#006A4E" + }, + { + "name": "Boysenberry", + "hex": "#873260" + }, + { + "name": "Brandeis Blue", + "hex": "#0070FF" + }, + { + "name": "Brass", + "hex": "#B5A642" + }, + { + "name": "Brick Red", + "hex": "#CB4154" + }, + { + "name": "Bright Cerulean", + "hex": "#1DACD6" + }, + { + "name": "Bright Green", + "hex": "#66FF00" + }, + { + "name": "Bright Lavender", + "hex": "#BF94E4" + }, + { + "name": "Bright Lilac", + "hex": "#D891EF" + }, + { + "name": "Bright Maroon", + "hex": "#C32148" + }, + { + "name": "Bright Navy Blue", + "hex": "#1974D2" + }, + { + "name": "Bright Pink", + "hex": "#FF007F" + }, + { + "name": "Bright Turquoise", + "hex": "#08E8DE" + }, + { + "name": "Bright Ube", + "hex": "#D19FE8" + }, + { + "name": "Bright Yellow (Crayola)", + "hex": "#FFAA1D" + }, + { + "name": "Brilliant Azure", + "hex": "#3399FF" + }, + { + "name": "Brilliant Lavender", + "hex": "#F4BBFF" + }, + { + "name": "Brilliant Rose", + "hex": "#FF55A3" + }, + { + "name": "Brink Pink", + "hex": "#FB607F" + }, + { + "name": "British Racing Green", + "hex": "#004225" + }, + { + "name": "Bronze", + "hex": "#CD7F32" + }, + { + "name": "Bronze Yellow", + "hex": "#737000" + }, + { + "name": "Brown (Traditional)", + "hex": "#964B00" + }, + { + "name": "Brown (Web)", + "hex": "#A52A2A" + }, + { + "name": "Brown-Nose", + "hex": "#6B4423" + }, + { + "name": "Brown Sugar", + "hex": "#AF6E4D" + }, + { + "name": "Brown Yellow", + "hex": "#cc9966" + }, + { + "name": "Brunswick Green", + "hex": "#1B4D3E" + }, + { + "name": "Bubble Gum", + "hex": "#FFC1CC" + }, + { + "name": "Bubbles", + "hex": "#E7FEFF" + }, + { + "name": "Bud Green", + "hex": "#7BB661" + }, + { + "name": "Buff", + "hex": "#F0DC82" + }, + { + "name": "Bulgarian Rose", + "hex": "#480607" + }, + { + "name": "Burgundy", + "hex": "#800020" + }, + { + "name": "Burlywood", + "hex": "#DEB887" + }, + { + "name": "Burnished Brown", + "hex": "#A17A74" + }, + { + "name": "Burnt Orange", + "hex": "#CC5500" + }, + { + "name": "Burnt Sienna", + "hex": "#E97451" + }, + { + "name": "Burnt Umber", + "hex": "#8A3324" + }, + { + "name": "Button Blue", + "hex": "#24A0ED" + }, + { + "name": "Byzantine", + "hex": "#BD33A4" + }, + { + "name": "Byzantium", + "hex": "#702963" + }, + { + "name": "Cadet", + "hex": "#536872" + }, + { + "name": "Cadet Blue", + "hex": "#5F9EA0" + }, + { + "name": "Cadet Grey", + "hex": "#91A3B0" + }, + { + "name": "Cadmium Green", + "hex": "#006B3C" + }, + { + "name": "Cadmium Orange", + "hex": "#ED872D" + }, + { + "name": "Cadmium Red", + "hex": "#E30022" + }, + { + "name": "Cadmium Yellow", + "hex": "#FFF600" + }, + { + "name": "Cafe Au Lait", + "hex": "#A67B5B" + }, + { + "name": "Cafe Noir", + "hex": "#4B3621" + }, + { + "name": "Cal Poly Pomona Green", + "hex": "#1E4D2B" + }, + { + "name": "Cambridge Blue", + "hex": "#A3C1AD" + }, + { + "name": "Camel", + "hex": "#C19A6B" + }, + { + "name": "Cameo Pink", + "hex": "#EFBBCC" + }, + { + "name": "Camouflage Green", + "hex": "#78866B" + }, + { + "name": "Canary", + "hex": "#FFFF99" + }, + { + "name": "Canary Yellow", + "hex": "#FFEF00" + }, + { + "name": "Candy Apple Red", + "hex": "#FF0800" + }, + { + "name": "Candy Pink", + "hex": "#E4717A" + }, + { + "name": "Capri", + "hex": "#00BFFF" + }, + { + "name": "Caput Mortuum", + "hex": "#592720" + }, + { + "name": "Cardinal", + "hex": "#C41E3A" + }, + { + "name": "Caribbean Green", + "hex": "#00CC99" + }, + { + "name": "Carmine", + "hex": "#960018" + }, + { + "name": "Carmine (M&P)", + "hex": "#D70040" + }, + { + "name": "Carmine Pink", + "hex": "#EB4C42" + }, + { + "name": "Carmine Red", + "hex": "#FF0038" + }, + { + "name": "Carnation Pink", + "hex": "#FFA6C9" + }, + { + "name": "Carnelian", + "hex": "#B31B1B" + }, + { + "name": "Carolina Blue", + "hex": "#56A0D3" + }, + { + "name": "Carrot Orange", + "hex": "#ED9121" + }, + { + "name": "Castleton Green", + "hex": "#00563F" + }, + { + "name": "Catalina Blue", + "hex": "#062A78" + }, + { + "name": "Catawba", + "hex": "#703642" + }, + { + "name": "Cedar Chest", + "hex": "#C95A49" + }, + { + "name": "Ceil", + "hex": "#92A1CF" + }, + { + "name": "Celadon", + "hex": "#ACE1AF" + }, + { + "name": "Celadon Blue", + "hex": "#007BA7" + }, + { + "name": "Celadon Green", + "hex": "#2F847C" + }, + { + "name": "Celeste", + "hex": "#B2FFFF" + }, + { + "name": "Celestial Blue", + "hex": "#4997D0" + }, + { + "name": "Cerise", + "hex": "#DE3163" + }, + { + "name": "Cerise Pink", + "hex": "#EC3B83" + }, + { + "name": "Cerulean", + "hex": "#007BA7" + }, + { + "name": "Cerulean Blue", + "hex": "#2A52BE" + }, + { + "name": "Cerulean Frost", + "hex": "#6D9BC3" + }, + { + "name": "CG Blue", + "hex": "#007AA5" + }, + { + "name": "CG Red", + "hex": "#E03C31" + }, + { + "name": "Chamoisee", + "hex": "#A0785A" + }, + { + "name": "Champagne", + "hex": "#F7E7CE" + }, + { + "name": "Champagne Pink", + "hex": "#F1DDCF" + }, + { + "name": "Charcoal", + "hex": "#36454F" + }, + { + "name": "Charleston Green", + "hex": "#232B2B" + }, + { + "name": "Charm Pink", + "hex": "#E68FAC" + }, + { + "name": "Chartreuse (Traditional)", + "hex": "#DFFF00" + }, + { + "name": "Chartreuse (Web)", + "hex": "#7FFF00" + }, + { + "name": "Cherry", + "hex": "#DE3163" + }, + { + "name": "Cherry Blossom Pink", + "hex": "#FFB7C5" + }, + { + "name": "Chestnut", + "hex": "#954535" + }, + { + "name": "China Pink", + "hex": "#DE6FA1" + }, + { + "name": "China Rose", + "hex": "#A8516E" + }, + { + "name": "Chinese Red", + "hex": "#AA381E" + }, + { + "name": "Chinese Violet", + "hex": "#856088" + }, + { + "name": "Chlorophyll Green", + "hex": "#4AFF00" + }, + { + "name": "Chocolate (Traditional)", + "hex": "#7B3F00" + }, + { + "name": "Chocolate (Web)", + "hex": "#D2691E" + }, + { + "name": "Chrome Yellow", + "hex": "#FFA700" + }, + { + "name": "Cinereous", + "hex": "#98817B" + }, + { + "name": "Cinnabar", + "hex": "#E34234" + }, + { + "name": "Cinnamon", + "hex": "#D2691E" + }, + { + "name": "Cinnamon Satin", + "hex": "#CD607E" + }, + { + "name": "Citrine", + "hex": "#E4D00A" + }, + { + "name": "Citron", + "hex": "#9FA91F" + }, + { + "name": "Claret", + "hex": "#7F1734" + }, + { + "name": "Classic Rose", + "hex": "#FBCCE7" + }, + { + "name": "Cobalt Blue", + "hex": "#0047AB" + }, + { + "name": "Cocoa Brown", + "hex": "#D2691E" + }, + { + "name": "Coconut", + "hex": "#965A3E" + }, + { + "name": "Coffee", + "hex": "#6F4E37" + }, + { + "name": "Columbia Blue", + "hex": "#C4D8E2" + }, + { + "name": "Congo Pink", + "hex": "#F88379" + }, + { + "name": "Cool Black", + "hex": "#002E63" + }, + { + "name": "Cool Grey", + "hex": "#8C92AC" + }, + { + "name": "Copper", + "hex": "#B87333" + }, + { + "name": "Copper (Crayola)", + "hex": "#DA8A67" + }, + { + "name": "Copper Penny", + "hex": "#AD6F69" + }, + { + "name": "Copper Red", + "hex": "#CB6D51" + }, + { + "name": "Copper Rose", + "hex": "#996666" + }, + { + "name": "Coquelicot", + "hex": "#FF3800" + }, + { + "name": "Coral", + "hex": "#FF7F50" + }, + { + "name": "Coral Pink", + "hex": "#F88379" + }, + { + "name": "Coral Red", + "hex": "#FF4040" + }, + { + "name": "Coral Reef", + "hex": "#FD7C6E" + }, + { + "name": "Cordovan", + "hex": "#893F45" + }, + { + "name": "Corn", + "hex": "#FBEC5D" + }, + { + "name": "Cornell Red", + "hex": "#B31B1B" + }, + { + "name": "Cornflower Blue", + "hex": "#6495ED" + }, + { + "name": "Cornsilk", + "hex": "#FFF8DC" + }, + { + "name": "Cosmic Cobalt", + "hex": "#2E2D88" + }, + { + "name": "Cosmic Latte", + "hex": "#FFF8E7" + }, + { + "name": "Coyote Brown", + "hex": "#81613C" + }, + { + "name": "Cotton Candy", + "hex": "#FFBCD9" + }, + { + "name": "Cream", + "hex": "#FFFDD0" + }, + { + "name": "Crimson", + "hex": "#DC143C" + }, + { + "name": "Crimson Glory", + "hex": "#BE0032" + }, + { + "name": "Crimson Red", + "hex": "#990000" + }, + { + "name": "Cultured", + "hex": "#F5F5F5" + }, + { + "name": "Cyan", + "hex": "#00FFFF" + }, + { + "name": "Cyan Azure", + "hex": "#4E82B4" + }, + { + "name": "Cyan-Blue Azure", + "hex": "#4682BF" + }, + { + "name": "Cyan Cobalt Blue", + "hex": "#28589C" + }, + { + "name": "Cyan Cornflower Blue", + "hex": "#188BC2" + }, + { + "name": "Cyan (Process)", + "hex": "#00B7EB" + }, + { + "name": "Cyber Grape", + "hex": "#58427C" + }, + { + "name": "Cyber Yellow", + "hex": "#FFD300" + }, + { + "name": "Cyclamen", + "hex": "#F56FA1" + }, + { + "name": "Daffodil", + "hex": "#FFFF31" + }, + { + "name": "Dandelion", + "hex": "#F0E130" + }, + { + "name": "Dark Blue", + "hex": "#00008B" + }, + { + "name": "Dark Blue-Gray", + "hex": "#666699" + }, + { + "name": "Dark Brown", + "hex": "#654321" + }, + { + "name": "Dark Brown-Tangelo", + "hex": "#88654E" + }, + { + "name": "Dark Byzantium", + "hex": "#5D3954" + }, + { + "name": "Dark Candy Apple Red", + "hex": "#A40000" + }, + { + "name": "Dark Cerulean", + "hex": "#08457E" + }, + { + "name": "Dark Chestnut", + "hex": "#986960" + }, + { + "name": "Dark Coral", + "hex": "#CD5B45" + }, + { + "name": "Dark Cyan", + "hex": "#008B8B" + }, + { + "name": "Dark Electric Blue", + "hex": "#536878" + }, + { + "name": "Dark Goldenrod", + "hex": "#B8860B" + }, + { + "name": "Dark Gray (X11)", + "hex": "#A9A9A9" + }, + { + "name": "Dark Green", + "hex": "#013220" + }, + { + "name": "Dark Green (X11)", + "hex": "#006400" + }, + { + "name": "Dark Gunmetal", + "hex": "#1F262A" + }, + { + "name": "Dark Imperial Blue", + "hex": "#00416A" + }, + { + "name": "Dark Imperial Blue", + "hex": "#00147E" + }, + { + "name": "Dark Jungle Green", + "hex": "#1A2421" + }, + { + "name": "Dark Khaki", + "hex": "#BDB76B" + }, + { + "name": "Dark Lava", + "hex": "#483C32" + }, + { + "name": "Dark Lavender", + "hex": "#734F96" + }, + { + "name": "Dark Liver", + "hex": "#534B4F" + }, + { + "name": "Dark Liver (Horses)", + "hex": "#543D37" + }, + { + "name": "Dark Magenta", + "hex": "#8B008B" + }, + { + "name": "Dark Medium Gray", + "hex": "#A9A9A9" + }, + { + "name": "Dark Midnight Blue", + "hex": "#003366" + }, + { + "name": "Dark Moss Green", + "hex": "#4A5D23" + }, + { + "name": "Dark Olive Green", + "hex": "#556B2F" + }, + { + "name": "Dark Orange", + "hex": "#FF8C00" + }, + { + "name": "Dark Orchid", + "hex": "#9932CC" + }, + { + "name": "Dark Pastel Blue", + "hex": "#779ECB" + }, + { + "name": "Dark Pastel Green", + "hex": "#03C03C" + }, + { + "name": "Dark Pastel Purple", + "hex": "#966FD6" + }, + { + "name": "Dark Pastel Red", + "hex": "#C23B22" + }, + { + "name": "Dark Pink", + "hex": "#E75480" + }, + { + "name": "Dark Powder Blue", + "hex": "#003399" + }, + { + "name": "Dark Puce", + "hex": "#4F3A3C" + }, + { + "name": "Dark Purple", + "hex": "#301934" + }, + { + "name": "Dark Raspberry", + "hex": "#872657" + }, + { + "name": "Dark Red", + "hex": "#8B0000" + }, + { + "name": "Dark Salmon", + "hex": "#E9967A" + }, + { + "name": "Dark Scarlet", + "hex": "#560319" + }, + { + "name": "Dark Sea Green", + "hex": "#8FBC8F" + }, + { + "name": "Dark Sienna", + "hex": "#3C1414" + }, + { + "name": "Dark Sky Blue", + "hex": "#8CBED6" + }, + { + "name": "Dark Slate Blue", + "hex": "#483D8B" + }, + { + "name": "Dark Slate Gray", + "hex": "#2F4F4F" + }, + { + "name": "Dark Spring Green", + "hex": "#177245" + }, + { + "name": "Dark Tan", + "hex": "#918151" + }, + { + "name": "Dark Tangerine", + "hex": "#FFA812" + }, + { + "name": "Dark Taupe", + "hex": "#483C32" + }, + { + "name": "Dark Terra Cotta", + "hex": "#CC4E5C" + }, + { + "name": "Dark Turquoise", + "hex": "#00CED1" + }, + { + "name": "Dark Vanilla", + "hex": "#D1BEA8" + }, + { + "name": "Dark Violet", + "hex": "#9400D3" + }, + { + "name": "Dark Yellow", + "hex": "#9B870C" + }, + { + "name": "Dartmouth Green", + "hex": "#00703C" + }, + { + "name": "Davy's Grey", + "hex": "#555555" + }, + { + "name": "Debian Red", + "hex": "#D70A53" + }, + { + "name": "Deep Aquamarine", + "hex": "#40826D" + }, + { + "name": "Deep Carmine", + "hex": "#A9203E" + }, + { + "name": "Deep Carmine Pink", + "hex": "#EF3038" + }, + { + "name": "Deep Carrot Orange", + "hex": "#E9692C" + }, + { + "name": "Deep Cerise", + "hex": "#DA3287" + }, + { + "name": "Deep Champagne", + "hex": "#FAD6A5" + }, + { + "name": "Deep Chestnut", + "hex": "#B94E48" + }, + { + "name": "Deep Coffee", + "hex": "#704241" + }, + { + "name": "Deep Fuchsia", + "hex": "#C154C1" + }, + { + "name": "Deep Green", + "hex": "#056608" + }, + { + "name": "Deep Green-Cyan Turquoise", + "hex": "#0E7C61" + }, + { + "name": "Deep Jungle Green", + "hex": "#004B49" + }, + { + "name": "Deep Koamaru", + "hex": "#333366" + }, + { + "name": "Deep Lemon", + "hex": "#F5C71A" + }, + { + "name": "Deep Lilac", + "hex": "#9955BB" + }, + { + "name": "Deep Magenta", + "hex": "#CC00CC" + }, + { + "name": "Deep Maroon", + "hex": "#820000" + }, + { + "name": "Deep Mauve", + "hex": "#D473D4" + }, + { + "name": "Deep Moss Green", + "hex": "#355E3B" + }, + { + "name": "Deep Peach", + "hex": "#FFCBA4" + }, + { + "name": "Deep Pink", + "hex": "#FF1493" + }, + { + "name": "Deep Puce", + "hex": "#A95C68" + }, + { + "name": "Deep Red", + "hex": "#850101" + }, + { + "name": "Deep Ruby", + "hex": "#843F5B" + }, + { + "name": "Deep Saffron", + "hex": "#FF9933" + }, + { + "name": "Deep Sky Blue", + "hex": "#00BFFF" + }, + { + "name": "Deep Space Sparkle", + "hex": "#4A646C" + }, + { + "name": "Deep Spring Bud", + "hex": "#556B2F" + }, + { + "name": "Deep Taupe", + "hex": "#7E5E60" + }, + { + "name": "Deep Tuscan Red", + "hex": "#66424D" + }, + { + "name": "Deep Violet", + "hex": "#330066" + }, + { + "name": "Deer", + "hex": "#BA8759" + }, + { + "name": "Denim", + "hex": "#1560BD" + }, + { + "name": "Denim Blue", + "hex": "#2243B6" + }, + { + "name": "Desaturated Cyan", + "hex": "#669999" + }, + { + "name": "Desert", + "hex": "#C19A6B" + }, + { + "name": "Desert Sand", + "hex": "#EDC9AF" + }, + { + "name": "Desire", + "hex": "#EA3C53" + }, + { + "name": "Diamond", + "hex": "#B9F2FF" + }, + { + "name": "Dim Gray", + "hex": "#696969" + }, + { + "name": "Dingy Dungeon", + "hex": "#C53151" + }, + { + "name": "Dirt", + "hex": "#9B7653" + }, + { + "name": "Dodger Blue", + "hex": "#1E90FF" + }, + { + "name": "Dodie Yellow", + "hex": "#FEF65B" + }, + { + "name": "Dogwood Rose", + "hex": "#D71868" + }, + { + "name": "Dollar Bill", + "hex": "#85BB65" + }, + { + "name": "Dolphin Gray", + "hex": "#828E84" + }, + { + "name": "Donkey Brown", + "hex": "#664C28" + }, + { + "name": "Drab", + "hex": "#967117" + }, + { + "name": "Duke Blue", + "hex": "#00009C" + }, + { + "name": "Dust Storm", + "hex": "#E5CCC9" + }, + { + "name": "Dutch White", + "hex": "#EFDFBB" + }, + { + "name": "Earth Yellow", + "hex": "#E1A95F" + }, + { + "name": "Ebony", + "hex": "#555D50" + }, + { + "name": "Ecru", + "hex": "#C2B280" + }, + { + "name": "Eerie Black", + "hex": "#1B1B1B" + }, + { + "name": "Eggplant", + "hex": "#614051" + }, + { + "name": "Eggshell", + "hex": "#F0EAD6" + }, + { + "name": "Egyptian Blue", + "hex": "#1034A6" + }, + { + "name": "Electric Blue", + "hex": "#7DF9FF" + }, + { + "name": "Electric Crimson", + "hex": "#FF003F" + }, + { + "name": "Electric Cyan", + "hex": "#00FFFF" + }, + { + "name": "Electric Green", + "hex": "#00FF00" + }, + { + "name": "Electric Indigo", + "hex": "#6F00FF" + }, + { + "name": "Electric Lavender", + "hex": "#F4BBFF" + }, + { + "name": "Electric Lime", + "hex": "#CCFF00" + }, + { + "name": "Electric Purple", + "hex": "#BF00FF" + }, + { + "name": "Electric Ultramarine", + "hex": "#3F00FF" + }, + { + "name": "Electric Violet", + "hex": "#8F00FF" + }, + { + "name": "Electric Yellow", + "hex": "#FFFF33" + }, + { + "name": "Emerald", + "hex": "#50C878" + }, + { + "name": "Eminence", + "hex": "#6C3082" + }, + { + "name": "English Green", + "hex": "#1B4D3E" + }, + { + "name": "English Lavender", + "hex": "#B48395" + }, + { + "name": "English Red", + "hex": "#AB4B52" + }, + { + "name": "English Vermillion", + "hex": "#CC474B" + }, + { + "name": "English Violet", + "hex": "#563C5C" + }, + { + "name": "Eton Blue", + "hex": "#96C8A2" + }, + { + "name": "Eucalyptus", + "hex": "#44D7A8" + }, + { + "name": "Fallow", + "hex": "#C19A6B" + }, + { + "name": "Falu Red", + "hex": "#801818" + }, + { + "name": "Fandango", + "hex": "#B53389" + }, + { + "name": "Fandango Pink", + "hex": "#DE5285" + }, + { + "name": "Fashion Fuchsia", + "hex": "#F400A1" + }, + { + "name": "Fawn", + "hex": "#E5AA70" + }, + { + "name": "Feldgrau", + "hex": "#4D5D53" + }, + { + "name": "Feldspar", + "hex": "#FDD5B1" + }, + { + "name": "Fern Green", + "hex": "#4F7942" + }, + { + "name": "Ferrari Red", + "hex": "#FF2800" + }, + { + "name": "Field Drab", + "hex": "#6C541E" + }, + { + "name": "Fiery Rose", + "hex": "#FF5470" + }, + { + "name": "Firebrick", + "hex": "#B22222" + }, + { + "name": "Fire Engine Red", + "hex": "#CE2029" + }, + { + "name": "Flame", + "hex": "#E25822" + }, + { + "name": "Flamingo Pink", + "hex": "#FC8EAC" + }, + { + "name": "Flattery", + "hex": "#6B4423" + }, + { + "name": "Flavescent", + "hex": "#F7E98E" + }, + { + "name": "Flax", + "hex": "#EEDC82" + }, + { + "name": "Flirt", + "hex": "#A2006D" + }, + { + "name": "Floral White", + "hex": "#FFFAF0" + }, + { + "name": "Fluorescent Orange", + "hex": "#FFBF00" + }, + { + "name": "Fluorescent Pink", + "hex": "#FF1493" + }, + { + "name": "Fluorescent Yellow", + "hex": "#CCFF00" + }, + { + "name": "Folly", + "hex": "#FF004F" + }, + { + "name": "Forest Green (Traditional)", + "hex": "#014421" + }, + { + "name": "Forest Green (Web)", + "hex": "#228B22" + }, + { + "name": "French Beige", + "hex": "#A67B5B" + }, + { + "name": "French Bistre", + "hex": "#856D4D" + }, + { + "name": "French Blue", + "hex": "#0072BB" + }, + { + "name": "French Fuchsia", + "hex": "#FD3F92" + }, + { + "name": "French Lilac", + "hex": "#86608E" + }, + { + "name": "French Lime", + "hex": "#9EFD38" + }, + { + "name": "French Mauve", + "hex": "#D473D4" + }, + { + "name": "French Pink", + "hex": "#FD6C9E" + }, + { + "name": "French Plum", + "hex": "#811453" + }, + { + "name": "French Puce", + "hex": "#4E1609" + }, + { + "name": "French Raspberry", + "hex": "#C72C48" + }, + { + "name": "French Rose", + "hex": "#F64A8A" + }, + { + "name": "French Sky Blue", + "hex": "#77B5FE" + }, + { + "name": "French Violet", + "hex": "#8806CE" + }, + { + "name": "French Wine", + "hex": "#AC1E44" + }, + { + "name": "Fresh Air", + "hex": "#A6E7FF" + }, + { + "name": "Frogert", + "hex": "#E936A7" + }, + { + "name": "Fuchsia", + "hex": "#FF00FF" + }, + { + "name": "Fuchsia (Crayola)", + "hex": "#C154C1" + }, + { + "name": "Fuchsia Pink", + "hex": "#FF77FF" + }, + { + "name": "Fuchsia Purple", + "hex": "#CC397B" + }, + { + "name": "Fuchsia Rose", + "hex": "#C74375" + }, + { + "name": "Fulvous", + "hex": "#E48400" + }, + { + "name": "Fuzzy Wuzzy", + "hex": "#CC6666" + }, + { + "name": "Gainsboro", + "hex": "#DCDCDC" + }, + { + "name": "Gamboge", + "hex": "#E49B0F" + }, + { + "name": "Gamboge Orange (Brown)", + "hex": "#996600" + }, + { + "name": "Gargoyle Gas", + "hex": "#FFDF46" + }, + { + "name": "Generic Viridian", + "hex": "#007F66" + }, + { + "name": "Ghost White", + "hex": "#F8F8FF" + }, + { + "name": "Giant's Club", + "hex": "#B05C52" + }, + { + "name": "Giants Orange", + "hex": "#FE5A1D" + }, + { + "name": "Ginger", + "hex": "#B06500" + }, + { + "name": "Glaucous", + "hex": "#6082B6" + }, + { + "name": "Glitter", + "hex": "#E6E8FA" + }, + { + "name": "Glossy Grape", + "hex": "#AB92B3" + }, + { + "name": "GO Green", + "hex": "#00AB66" + }, + { + "name": "Gold (Metallic)", + "hex": "#D4AF37" + }, + { + "name": "Gold (Web) (Golden)", + "hex": "#FFD700" + }, + { + "name": "Gold Fusion", + "hex": "#85754E" + }, + { + "name": "Golden Brown", + "hex": "#996515" + }, + { + "name": "Golden Poppy", + "hex": "#FCC200" + }, + { + "name": "Golden Yellow", + "hex": "#FFDF00" + }, + { + "name": "Goldenrod", + "hex": "#DAA520" + }, + { + "name": "Granite Gray", + "hex": "#676767" + }, + { + "name": "Granny Smith Apple", + "hex": "#A8E4A0" + }, + { + "name": "Grape", + "hex": "#6F2DA8" + }, + { + "name": "Gray", + "hex": "#808080" + }, + { + "name": "Gray (HTML/CSS Gray)", + "hex": "#808080" + }, + { + "name": "Gray (X11 Gray)", + "hex": "#BEBEBE" + }, + { + "name": "Gray-Asparagus", + "hex": "#465945" + }, + { + "name": "Gray-Blue", + "hex": "#8C92AC" + }, + { + "name": "Green (Color Wheel) (X11 Green)", + "hex": "#00FF00" + }, + { + "name": "Green (Crayola)", + "hex": "#1CAC78" + }, + { + "name": "Green (HTML/CSS Color)", + "hex": "#008000" + }, + { + "name": "Green (Munsell)", + "hex": "#00A877" + }, + { + "name": "Green (NCS)", + "hex": "#009F6B" + }, + { + "name": "Green (Pantone)", + "hex": "#00AD43" + }, + { + "name": "Green (Pigment)", + "hex": "#00A550" + }, + { + "name": "Green (RYB)", + "hex": "#66B032" + }, + { + "name": "Green-Blue", + "hex": "#1164B4" + }, + { + "name": "Green-Cyan", + "hex": "#009966" + }, + { + "name": "Green Lizard", + "hex": "#A7F432" + }, + { + "name": "Green Sheen", + "hex": "#6EAEA1" + }, + { + "name": "Green-Yellow", + "hex": "#ADFF2F" + }, + { + "name": "Grizzly", + "hex": "#885818" + }, + { + "name": "Grullo", + "hex": "#A99A86" + }, + { + "name": "Guppie Green", + "hex": "#00FF7F" + }, + { + "name": "Gunmetal", + "hex": "#2a3439" + }, + { + "name": "Halaya Ube", + "hex": "#663854" + }, + { + "name": "Han Blue", + "hex": "#446CCF" + }, + { + "name": "Han Purple", + "hex": "#5218FA" + }, + { + "name": "Hansa Yellow", + "hex": "#E9D66B" + }, + { + "name": "Harlequin", + "hex": "#3FFF00" + }, + { + "name": "Harlequin Green", + "hex": "#46CB18" + }, + { + "name": "Harvard Crimson", + "hex": "#C90016" + }, + { + "name": "Harvest Gold", + "hex": "#DA9100" + }, + { + "name": "Heart Gold", + "hex": "#808000" + }, + { + "name": "Heat Wave", + "hex": "#FF7A00" + }, + { + "name": "Heidelberg Red", + "hex": "#960018" + }, + { + "name": "Heliotrope", + "hex": "#DF73FF" + }, + { + "name": "Heliotrope Gray", + "hex": "#AA98A9" + }, + { + "name": "Heliotrope Magenta", + "hex": "#AA00BB" + }, + { + "name": "Hollywood Cerise", + "hex": "#F400A1" + }, + { + "name": "Honeydew", + "hex": "#F0FFF0" + }, + { + "name": "Honolulu Blue", + "hex": "#006DB0" + }, + { + "name": "Hooker's Green", + "hex": "#49796B" + }, + { + "name": "Hot Magenta", + "hex": "#FF1DCE" + }, + { + "name": "Hot Pink", + "hex": "#FF69B4" + }, + { + "name": "Hunter Green", + "hex": "#355E3B" + }, + { + "name": "Iceberg", + "hex": "#71A6D2" + }, + { + "name": "Icterine", + "hex": "#FCF75E" + }, + { + "name": "Iguana Green", + "hex": "#71BC78" + }, + { + "name": "Illuminating Emerald", + "hex": "#319177" + }, + { + "name": "Imperial", + "hex": "#602F6B" + }, + { + "name": "Imperial Blue", + "hex": "#002395" + }, + { + "name": "Imperial Purple", + "hex": "#66023C" + }, + { + "name": "Imperial Red", + "hex": "#ED2939" + }, + { + "name": "Inchworm", + "hex": "#B2EC5D" + }, + { + "name": "Independence", + "hex": "#4C516D" + }, + { + "name": "India Green", + "hex": "#138808" + }, + { + "name": "Indian Red", + "hex": "#CD5C5C" + }, + { + "name": "Indian Yellow", + "hex": "#E3A857" + }, + { + "name": "Indigo", + "hex": "#4B0082" + }, + { + "name": "Indigo Dye", + "hex": "#091F92" + }, + { + "name": "Indigo (Web)", + "hex": "#4B0082" + }, + { + "name": "Infra Red", + "hex": "#FF496C" + }, + { + "name": "Interdimensional Blue", + "hex": "#360CCC" + }, + { + "name": "International Klein Blue", + "hex": "#002FA7" + }, + { + "name": "International Orange (Aerospace)", + "hex": "#FF4F00" + }, + { + "name": "International Orange (Engineering)", + "hex": "#BA160C" + }, + { + "name": "International Orange (Golden Gate Bridge)", + "hex": "#C0362C" + }, + { + "name": "Iris", + "hex": "#5A4FCF" + }, + { + "name": "Irresistible", + "hex": "#B3446C" + }, + { + "name": "Isabelline", + "hex": "#F4F0EC" + }, + { + "name": "Islamic Green", + "hex": "#009000" + }, + { + "name": "Italian Sky Blue", + "hex": "#B2FFFF" + }, + { + "name": "Ivory", + "hex": "#FFFFF0" + }, + { + "name": "Jade", + "hex": "#00A86B" + }, + { + "name": "Japanese Carmine", + "hex": "#9D2933" + }, + { + "name": "Japanese Indigo", + "hex": "#264348" + }, + { + "name": "Japanese Violet", + "hex": "#5B3256" + }, + { + "name": "Jasmine", + "hex": "#F8DE7E" + }, + { + "name": "Jasper", + "hex": "#D73B3E" + }, + { + "name": "Jazzberry Jam", + "hex": "#A50B5E" + }, + { + "name": "Jelly Bean", + "hex": "#DA614E" + }, + { + "name": "Jet", + "hex": "#343434" + }, + { + "name": "Jonquil", + "hex": "#F4CA16" + }, + { + "name": "Jordy Blue", + "hex": "#8AB9F1" + }, + { + "name": "June Bud", + "hex": "#BDDA57" + }, + { + "name": "Jungle Green", + "hex": "#29AB87" + }, + { + "name": "Kelly Green", + "hex": "#4CBB17" + }, + { + "name": "Kenyan Copper", + "hex": "#7C1C05" + }, + { + "name": "Keppel", + "hex": "#3AB09E" + }, + { + "name": "Key Lime", + "hex": "#E8F48C" + }, + { + "name": "Khaki (HTML/CSS) (Khaki)", + "hex": "#C3B091" + }, + { + "name": "Khaki (X11) (Light Khaki)", + "hex": "#F0E68C" + }, + { + "name": "Kiwi", + "hex": "#8EE53F" + }, + { + "name": "Kobe", + "hex": "#882D17" + }, + { + "name": "Kobi", + "hex": "#E79FC4" + }, + { + "name": "Kobicha", + "hex": "#6B4423" + }, + { + "name": "Kombu Green", + "hex": "#354230" + }, + { + "name": "KSU Purple", + "hex": "#512888" + }, + { + "name": "KU Crimson", + "hex": "#E8000D" + }, + { + "name": "La Salle Green", + "hex": "#087830" + }, + { + "name": "Languid Lavender", + "hex": "#D6CADD" + }, + { + "name": "Lapis Lazuli", + "hex": "#26619C" + }, + { + "name": "Laser Lemon", + "hex": "#FFFF66" + }, + { + "name": "Laurel Green", + "hex": "#A9BA9D" + }, + { + "name": "Lava", + "hex": "#CF1020" + }, + { + "name": "Lavender (Floral)", + "hex": "#B57EDC" + }, + { + "name": "Lavender (Web)", + "hex": "#E6E6FA" + }, + { + "name": "Lavender Blue", + "hex": "#CCCCFF" + }, + { + "name": "Lavender Blush", + "hex": "#FFF0F5" + }, + { + "name": "Lavender Gray", + "hex": "#C4C3D0" + }, + { + "name": "Lavender Indigo", + "hex": "#9457EB" + }, + { + "name": "Lavender Magenta", + "hex": "#EE82EE" + }, + { + "name": "Lavender Mist", + "hex": "#E6E6FA" + }, + { + "name": "Lavender Pink", + "hex": "#FBAED2" + }, + { + "name": "Lavender Purple", + "hex": "#967BB6" + }, + { + "name": "Lavender Rose", + "hex": "#FBA0E3" + }, + { + "name": "Lawn Green", + "hex": "#7CFC00" + }, + { + "name": "Lemon", + "hex": "#FFF700" + }, + { + "name": "Lemon Chiffon", + "hex": "#FFFACD" + }, + { + "name": "Lemon Curry", + "hex": "#CCA01D" + }, + { + "name": "Lemon Glacier", + "hex": "#FDFF00" + }, + { + "name": "Lemon Lime", + "hex": "#E3FF00" + }, + { + "name": "Lemon Meringue", + "hex": "#F6EABE" + }, + { + "name": "Lemon Yellow", + "hex": "#FFF44F" + }, + { + "name": "Licorice", + "hex": "#1A1110" + }, + { + "name": "Liberty", + "hex": "#545AA7" + }, + { + "name": "Light Apricot", + "hex": "#FDD5B1" + }, + { + "name": "Light Blue", + "hex": "#ADD8E6" + }, + { + "name": "Light Brown", + "hex": "#B5651D" + }, + { + "name": "Light Carmine Pink", + "hex": "#E66771" + }, + { + "name": "Light Cobalt Blue", + "hex": "#88ACE0" + }, + { + "name": "Light Coral", + "hex": "#F08080" + }, + { + "name": "Light Cornflower Blue", + "hex": "#93CCEA" + }, + { + "name": "Light Crimson", + "hex": "#F56991" + }, + { + "name": "Light Cyan", + "hex": "#E0FFFF" + }, + { + "name": "Light Deep Pink", + "hex": "#FF5CCD" + }, + { + "name": "Light French Beige", + "hex": "#C8AD7F" + }, + { + "name": "Light Fuchsia Pink", + "hex": "#F984EF" + }, + { + "name": "Light Goldenrod Yellow", + "hex": "#FAFAD2" + }, + { + "name": "Light Gray", + "hex": "#D3D3D3" + }, + { + "name": "Light Grayish Magenta", + "hex": "#CC99CC" + }, + { + "name": "Light Green", + "hex": "#90EE90" + }, + { + "name": "Light Hot Pink", + "hex": "#FFB3DE" + }, + { + "name": "Light Khaki", + "hex": "#F0E68C" + }, + { + "name": "Light Medium Orchid", + "hex": "#D39BCB" + }, + { + "name": "Light Moss Green", + "hex": "#ADDFAD" + }, + { + "name": "Light Orange", + "hex": "#FED8B1" + }, + { + "name": "Light Orchid", + "hex": "#E6A8D7" + }, + { + "name": "Light Pastel Purple", + "hex": "#B19CD9" + }, + { + "name": "Light Pink", + "hex": "#FFB6C1" + }, + { + "name": "Light Red Ochre", + "hex": "#E97451" + }, + { + "name": "Light Salmon", + "hex": "#FFA07A" + }, + { + "name": "Light Salmon Pink", + "hex": "#FF9999" + }, + { + "name": "Light Sea Green", + "hex": "#20B2AA" + }, + { + "name": "Light Sky Blue", + "hex": "#87CEFA" + }, + { + "name": "Light Slate Gray", + "hex": "#778899" + }, + { + "name": "Light Steel Blue", + "hex": "#B0C4DE" + }, + { + "name": "Light Taupe", + "hex": "#B38B6D" + }, + { + "name": "Light Thulian Pink", + "hex": "#E68FAC" + }, + { + "name": "Light Yellow", + "hex": "#FFFFE0" + }, + { + "name": "Lilac", + "hex": "#C8A2C8" + }, + { + "name": "Lilac Luster", + "hex": "#AE98AA" + }, + { + "name": "Lime (Color Wheel)", + "hex": "#BFFF00" + }, + { + "name": "Lime (Web) (X11 Green)", + "hex": "#00FF00" + }, + { + "name": "Lime Green", + "hex": "#32CD32" + }, + { + "name": "Limerick", + "hex": "#9DC209" + }, + { + "name": "Lincoln Green", + "hex": "#195905" + }, + { + "name": "Linen", + "hex": "#FAF0E6" + }, + { + "name": "Loeen (Lopen) Look", + "hex": "#15F2FD" + }, + { + "name": "Liseran Purple", + "hex": "#DE6FA1" + }, + { + "name": "Little Boy Blue", + "hex": "#6CA0DC" + }, + { + "name": "Liver", + "hex": "#674C47" + }, + { + "name": "Liver (Dogs)", + "hex": "#B86D29" + }, + { + "name": "Liver (Organ)", + "hex": "#6C2E1F" + }, + { + "name": "Liver Chestnut", + "hex": "#987456" + }, + { + "name": "Livid", + "hex": "#6699CC" + }, + { + "name": "Lumber", + "hex": "#FFE4CD" + }, + { + "name": "Lust", + "hex": "#E62020" + }, + { + "name": "Maastricht Blue", + "hex": "#001C3D" + }, + { + "name": "Macaroni And Cheese", + "hex": "#FFBD88" + }, + { + "name": "Madder Lake", + "hex": "#CC3336" + }, + { + "name": "Magenta", + "hex": "#FF00FF" + }, + { + "name": "Magenta (Crayola)", + "hex": "#FF55A3" + }, + { + "name": "Magenta (Dye)", + "hex": "#CA1F7B" + }, + { + "name": "Magenta (Pantone)", + "hex": "#D0417E" + }, + { + "name": "Magenta (Process)", + "hex": "#FF0090" + }, + { + "name": "Magenta Haze", + "hex": "#9F4576" + }, + { + "name": "Magenta-Pink", + "hex": "#CC338B" + }, + { + "name": "Magic Mint", + "hex": "#AAF0D1" + }, + { + "name": "Magic Potion", + "hex": "#FF4466" + }, + { + "name": "Magnolia", + "hex": "#F8F4FF" + }, + { + "name": "Mahogany", + "hex": "#C04000" + }, + { + "name": "Maize", + "hex": "#FBEC5D" + }, + { + "name": "Majorelle Blue", + "hex": "#6050DC" + }, + { + "name": "Malachite", + "hex": "#0BDA51" + }, + { + "name": "Manatee", + "hex": "#979AAA" + }, + { + "name": "Mandarin", + "hex": "#F37A48" + }, + { + "name": "Mango Tango", + "hex": "#FF8243" + }, + { + "name": "Mantis", + "hex": "#74C365" + }, + { + "name": "Mardi Gras", + "hex": "#880085" + }, + { + "name": "Marigold", + "hex": "#EAA221" + }, + { + "name": "Maroon (Crayola)", + "hex": "#C32148" + }, + { + "name": "Maroon (HTML/CSS)", + "hex": "#800000" + }, + { + "name": "Maroon (X11)", + "hex": "#B03060" + }, + { + "name": "Mauve", + "hex": "#E0B0FF" + }, + { + "name": "Mauve Taupe", + "hex": "#915F6D" + }, + { + "name": "Mauvelous", + "hex": "#EF98AA" + }, + { + "name": "Maximum Blue", + "hex": "#47ABCC" + }, + { + "name": "Maximum Blue Green", + "hex": "#30BFBF" + }, + { + "name": "Maximum Blue Purple", + "hex": "#ACACE6" + }, + { + "name": "Maximum Green", + "hex": "#5E8C31" + }, + { + "name": "Maximum Green Yellow", + "hex": "#D9E650" + }, + { + "name": "Maximum Purple", + "hex": "#733380" + }, + { + "name": "Maximum Red", + "hex": "#D92121" + }, + { + "name": "Maximum Red Purple", + "hex": "#A63A79" + }, + { + "name": "Maximum Yellow", + "hex": "#FAFA37" + }, + { + "name": "Maximum Yellow Red", + "hex": "#F2BA49" + }, + { + "name": "May Green", + "hex": "#4C9141" + }, + { + "name": "Maya Blue", + "hex": "#73C2FB" + }, + { + "name": "Meat Brown", + "hex": "#E5B73B" + }, + { + "name": "Medium Aquamarine", + "hex": "#66DDAA" + }, + { + "name": "Medium Blue", + "hex": "#0000CD" + }, + { + "name": "Medium Candy Apple Red", + "hex": "#E2062C" + }, + { + "name": "Medium Carmine", + "hex": "#AF4035" + }, + { + "name": "Medium Champagne", + "hex": "#F3E5AB" + }, + { + "name": "Medium Electric Blue", + "hex": "#035096" + }, + { + "name": "Medium Jungle Green", + "hex": "#1C352D" + }, + { + "name": "Medium Lavender Magenta", + "hex": "#DDA0DD" + }, + { + "name": "Medium Orchid", + "hex": "#BA55D3" + }, + { + "name": "Medium Persian Blue", + "hex": "#0067A5" + }, + { + "name": "Medium Purple", + "hex": "#9370DB" + }, + { + "name": "Medium Red-Violet", + "hex": "#BB3385" + }, + { + "name": "Medium Ruby", + "hex": "#AA4069" + }, + { + "name": "Medium Sea Green", + "hex": "#3CB371" + }, + { + "name": "Medium Sky Blue", + "hex": "#80DAEB" + }, + { + "name": "Medium Slate Blue", + "hex": "#7B68EE" + }, + { + "name": "Medium Spring Bud", + "hex": "#C9DC87" + }, + { + "name": "Medium Spring Green", + "hex": "#00FA9A" + }, + { + "name": "Medium Taupe", + "hex": "#674C47" + }, + { + "name": "Medium Turquoise", + "hex": "#48D1CC" + }, + { + "name": "Medium Tuscan Red", + "hex": "#79443B" + }, + { + "name": "Medium Vermilion", + "hex": "#D9603B" + }, + { + "name": "Medium Violet-Red", + "hex": "#C71585" + }, + { + "name": "Mellow Apricot", + "hex": "#F8B878" + }, + { + "name": "Mellow Yellow", + "hex": "#F8DE7E" + }, + { + "name": "Melon", + "hex": "#FDBCB4" + }, + { + "name": "Metallic Seaweed", + "hex": "#0A7E8C" + }, + { + "name": "Metallic Sunburst", + "hex": "#9C7C38" + }, + { + "name": "Mexican Pink", + "hex": "#E4007C" + }, + { + "name": "Middle Blue", + "hex": "#7ED4E6" + }, + { + "name": "Middle Blue Green", + "hex": "#8DD9CC" + }, + { + "name": "Middle Blue Purple", + "hex": "#8B72BE" + }, + { + "name": "Middle Red Purple", + "hex": "#210837" + }, + { + "name": "Middle Green", + "hex": "#4D8C57" + }, + { + "name": "Middle Green Yellow", + "hex": "#ACBF60" + }, + { + "name": "Middle Purple", + "hex": "#D982B5" + }, + { + "name": "Middle Red", + "hex": "#E58E73" + }, + { + "name": "Middle Red Purple", + "hex": "#A55353" + }, + { + "name": "Middle Yellow", + "hex": "#FFEB00" + }, + { + "name": "Middle Yellow Red", + "hex": "#ECB176" + }, + { + "name": "Midnight", + "hex": "#702670" + }, + { + "name": "Midnight Blue", + "hex": "#191970" + }, + { + "name": "Midnight Green (Eagle Green)", + "hex": "#004953" + }, + { + "name": "Mikado Yellow", + "hex": "#FFC40C" + }, + { + "name": "Milk", + "hex": "#FDFFF5" + }, + { + "name": "Mimi Pink", + "hex": "#FFDAE9" + }, + { + "name": "Mindaro", + "hex": "#E3F988" + }, + { + "name": "Ming", + "hex": "#36747D" + }, + { + "name": "Minion Yellow", + "hex": "#F5E050" + }, + { + "name": "Mint", + "hex": "#3EB489" + }, + { + "name": "Mint Cream", + "hex": "#F5FFFA" + }, + { + "name": "Mint Green", + "hex": "#98FF98" + }, + { + "name": "Misty Moss", + "hex": "#BBB477" + }, + { + "name": "Misty Rose", + "hex": "#FFE4E1" + }, + { + "name": "Moccasin", + "hex": "#FAEBD7" + }, + { + "name": "Mode Beige", + "hex": "#967117" + }, + { + "name": "Moonstone Blue", + "hex": "#73A9C2" + }, + { + "name": "Mordant Red 19", + "hex": "#AE0C00" + }, + { + "name": "Morning Blue", + "hex": "#8DA399" + }, + { + "name": "Moss Green", + "hex": "#8A9A5B" + }, + { + "name": "Mountain Meadow", + "hex": "#30BA8F" + }, + { + "name": "Mountbatten Pink", + "hex": "#997A8D" + }, + { + "name": "MSU Green", + "hex": "#18453B" + }, + { + "name": "Mughal Green", + "hex": "#306030" + }, + { + "name": "Mulberry", + "hex": "#C54B8C" + }, + { + "name": "Mummy's Tomb", + "hex": "#828E84" + }, + { + "name": "Mustard", + "hex": "#FFDB58" + }, + { + "name": "Myrtle Green", + "hex": "#317873" + }, + { + "name": "Mystic", + "hex": "#D65282" + }, + { + "name": "Mystic Maroon", + "hex": "#AD4379" + }, + { + "name": "Nadeshiko Pink", + "hex": "#F6ADC6" + }, + { + "name": "Napier Green", + "hex": "#2A8000" + }, + { + "name": "Naples Yellow", + "hex": "#FADA5E" + }, + { + "name": "Navajo White", + "hex": "#FFDEAD" + }, + { + "name": "Navy", + "hex": "#000080" + }, + { + "name": "Navy Purple", + "hex": "#9457EB" + }, + { + "name": "Neon Carrot", + "hex": "#FFA343" + }, + { + "name": "Neon Fuchsia", + "hex": "#FE4164" + }, + { + "name": "Neon Green", + "hex": "#39FF14" + }, + { + "name": "New Car", + "hex": "#214FC6" + }, + { + "name": "New York Pink", + "hex": "#D7837F" + }, + { + "name": "Nickel", + "hex": "#727472" + }, + { + "name": "Non-Photo Blue", + "hex": "#A4DDED" + }, + { + "name": "North Texas Green", + "hex": "#059033" + }, + { + "name": "Nyanza", + "hex": "#E9FFDB" + }, + { + "name": "Ocean Blue", + "hex": "#4F42B5" + }, + { + "name": "Ocean Boat Blue", + "hex": "#0077BE" + }, + { + "name": "Ocean Green", + "hex": "#48BF91" + }, + { + "name": "Ochre", + "hex": "#CC7722" + }, + { + "name": "Office Green", + "hex": "#008000" + }, + { + "name": "Ogre Odor", + "hex": "#FD5240" + }, + { + "name": "Old Burgundy", + "hex": "#43302E" + }, + { + "name": "Old Gold", + "hex": "#CFB53B" + }, + { + "name": "Old Heliotrope", + "hex": "#563C5C" + }, + { + "name": "Old Lace", + "hex": "#FDF5E6" + }, + { + "name": "Old Lavender", + "hex": "#796878" + }, + { + "name": "Old Mauve", + "hex": "#673147" + }, + { + "name": "Old Moss Green", + "hex": "#867E36" + }, + { + "name": "Old Rose", + "hex": "#C08081" + }, + { + "name": "Old Silver", + "hex": "#848482" + }, + { + "name": "Olive", + "hex": "#808000" + }, + { + "name": "Olive Drab (#3)", + "hex": "#6B8E23" + }, + { + "name": "Olive Drab #7", + "hex": "#3C341F" + }, + { + "name": "Olivine", + "hex": "#9AB973" + }, + { + "name": "Onyx", + "hex": "#353839" + }, + { + "name": "Opera Mauve", + "hex": "#B784A7" + }, + { + "name": "Orange (Color Wheel)", + "hex": "#FF7F00" + }, + { + "name": "Orange (Crayola)", + "hex": "#FF7538" + }, + { + "name": "Orange (Pantone)", + "hex": "#FF5800" + }, + { + "name": "Orange (RYB)", + "hex": "#FB9902" + }, + { + "name": "Orange (Web)", + "hex": "#FFA500" + }, + { + "name": "Orange Peel", + "hex": "#FF9F00" + }, + { + "name": "Orange-Red", + "hex": "#FF4500" + }, + { + "name": "Orange Soda", + "hex": "#FA5B3D" + }, + { + "name": "Orange-Yellow", + "hex": "#F8D568" + }, + { + "name": "Orchid", + "hex": "#DA70D6" + }, + { + "name": "Orchid Pink", + "hex": "#F2BDCD" + }, + { + "name": "Orioles Orange", + "hex": "#FB4F14" + }, + { + "name": "Otter Brown", + "hex": "#654321" + }, + { + "name": "Outer Space", + "hex": "#414A4C" + }, + { + "name": "Outrageous Orange", + "hex": "#FF6E4A" + }, + { + "name": "Oxford Blue", + "hex": "#002147" + }, + { + "name": "OU Crimson Red", + "hex": "#990000" + }, + { + "name": "Pacific Blue", + "hex": "#1CA9C9" + }, + { + "name": "Pakistan Green", + "hex": "#006600" + }, + { + "name": "Palatinate Blue", + "hex": "#273BE2" + }, + { + "name": "Palatinate Purple", + "hex": "#682860" + }, + { + "name": "Pale Aqua", + "hex": "#BCD4E6" + }, + { + "name": "Pale Blue", + "hex": "#AFEEEE" + }, + { + "name": "Pale Brown", + "hex": "#987654" + }, + { + "name": "Pale Carmine", + "hex": "#AF4035" + }, + { + "name": "Pale Cerulean", + "hex": "#9BC4E2" + }, + { + "name": "Pale Chestnut", + "hex": "#DDADAF" + }, + { + "name": "Pale Copper", + "hex": "#DA8A67" + }, + { + "name": "Pale Cornflower Blue", + "hex": "#ABCDEF" + }, + { + "name": "Pale Cyan", + "hex": "#87D3F8" + }, + { + "name": "Pale Gold", + "hex": "#E6BE8A" + }, + { + "name": "Pale Goldenrod", + "hex": "#EEE8AA" + }, + { + "name": "Pale Green", + "hex": "#98FB98" + }, + { + "name": "Pale Lavender", + "hex": "#DCD0FF" + }, + { + "name": "Pale Magenta", + "hex": "#F984E5" + }, + { + "name": "Pale Magenta-Pink", + "hex": "#FF99CC" + }, + { + "name": "Pale Pink", + "hex": "#FADADD" + }, + { + "name": "Pale Plum", + "hex": "#DDA0DD" + }, + { + "name": "Pale Red-Violet", + "hex": "#DB7093" + }, + { + "name": "Pale Robin Egg Blue", + "hex": "#96DED1" + }, + { + "name": "Pale Silver", + "hex": "#C9C0BB" + }, + { + "name": "Pale Spring Bud", + "hex": "#ECEBBD" + }, + { + "name": "Pale Taupe", + "hex": "#BC987E" + }, + { + "name": "Pale Turquoise", + "hex": "#AFEEEE" + }, + { + "name": "Pale Violet", + "hex": "#CC99FF" + }, + { + "name": "Pale Violet-Red", + "hex": "#DB7093" + }, + { + "name": "Palm Leaf", + "hex": "#6F9940" + }, + { + "name": "Pansy Purple", + "hex": "#78184A" + }, + { + "name": "Paolo Veronese Green", + "hex": "#009B7D" + }, + { + "name": "Papaya Whip", + "hex": "#FFEFD5" + }, + { + "name": "Paradise Pink", + "hex": "#E63E62" + }, + { + "name": "Paris Green", + "hex": "#50C878" + }, + { + "name": "Parrot Pink", + "hex": "#D998A0" + }, + { + "name": "Pastel Blue", + "hex": "#AEC6CF" + }, + { + "name": "Pastel Brown", + "hex": "#836953" + }, + { + "name": "Pastel Gray", + "hex": "#CFCFC4" + }, + { + "name": "Pastel Green", + "hex": "#77DD77" + }, + { + "name": "Pastel Magenta", + "hex": "#F49AC2" + }, + { + "name": "Pastel Orange", + "hex": "#FFB347" + }, + { + "name": "Pastel Pink", + "hex": "#DEA5A4" + }, + { + "name": "Pastel Purple", + "hex": "#B39EB5" + }, + { + "name": "Pastel Red", + "hex": "#FF6961" + }, + { + "name": "Pastel Violet", + "hex": "#CB99C9" + }, + { + "name": "Pastel Yellow", + "hex": "#FDFD96" + }, + { + "name": "Patriarch", + "hex": "#800080" + }, + { + "name": "Payne's Grey", + "hex": "#536878" + }, + { + "name": "Peach", + "hex": "#FFE5B4" + }, + { + "name": "Peach", + "hex": "#FFCBA4" + }, + { + "name": "Peach-Orange", + "hex": "#FFCC99" + }, + { + "name": "Peach Puff", + "hex": "#FFDAB9" + }, + { + "name": "Peach-Yellow", + "hex": "#FADFAD" + }, + { + "name": "Pear", + "hex": "#D1E231" + }, + { + "name": "Pearl", + "hex": "#EAE0C8" + }, + { + "name": "Pearl Aqua", + "hex": "#88D8C0" + }, + { + "name": "Pearly Purple", + "hex": "#B768A2" + }, + { + "name": "Peridot", + "hex": "#E6E200" + }, + { + "name": "Periwinkle", + "hex": "#CCCCFF" + }, + { + "name": "Permanent Geranium Lake", + "hex": "#E12C2C" + }, + { + "name": "Persian Blue", + "hex": "#1C39BB" + }, + { + "name": "Persian Green", + "hex": "#00A693" + }, + { + "name": "Persian Indigo", + "hex": "#32127A" + }, + { + "name": "Persian Orange", + "hex": "#D99058" + }, + { + "name": "Persian Pink", + "hex": "#F77FBE" + }, + { + "name": "Persian Plum", + "hex": "#701C1C" + }, + { + "name": "Persian Red", + "hex": "#CC3333" + }, + { + "name": "Persian Rose", + "hex": "#FE28A2" + }, + { + "name": "Persimmon", + "hex": "#EC5800" + }, + { + "name": "Peru", + "hex": "#CD853F" + }, + { + "name": "Pewter Blue", + "hex": "#8BA8B7" + }, + { + "name": "Phlox", + "hex": "#DF00FF" + }, + { + "name": "Phthalo Blue", + "hex": "#000F89" + }, + { + "name": "Phthalo Green", + "hex": "#123524" + }, + { + "name": "Picton Blue", + "hex": "#45B1E8" + }, + { + "name": "Pictorial Carmine", + "hex": "#C30B4E" + }, + { + "name": "Piggy Pink", + "hex": "#FDDDE6" + }, + { + "name": "Pine Green", + "hex": "#01796F" + }, + { + "name": "Pineapple", + "hex": "#563C0D" + }, + { + "name": "Pink", + "hex": "#FFC0CB" + }, + { + "name": "Pink (Pantone)", + "hex": "#D74894" + }, + { + "name": "Pink Flamingo", + "hex": "#FC74FD" + }, + { + "name": "Pink Lace", + "hex": "#FFDDF4" + }, + { + "name": "Pink Lavender", + "hex": "#D8B2D1" + }, + { + "name": "Pink-Orange", + "hex": "#FF9966" + }, + { + "name": "Pink Pearl", + "hex": "#E7ACCF" + }, + { + "name": "Pink Raspberry", + "hex": "#980036" + }, + { + "name": "Pink Sherbet", + "hex": "#F78FA7" + }, + { + "name": "Pistachio", + "hex": "#93C572" + }, + { + "name": "Pixie Powder", + "hex": "#391285" + }, + { + "name": "Platinum", + "hex": "#E5E4E2" + }, + { + "name": "Plum", + "hex": "#8E4585" + }, + { + "name": "Plum (Web)", + "hex": "#DDA0DD" + }, + { + "name": "Plump Purple", + "hex": "#5946B2" + }, + { + "name": "Polished Pine", + "hex": "#5DA493" + }, + { + "name": "Pomp And Power", + "hex": "#86608E" + }, + { + "name": "Popstar", + "hex": "#BE4F62" + }, + { + "name": "Portland Orange", + "hex": "#FF5A36" + }, + { + "name": "Powder Blue", + "hex": "#B0E0E6" + }, + { + "name": "Princess Perfume", + "hex": "#FF85CF" + }, + { + "name": "Princeton Orange", + "hex": "#F58025" + }, + { + "name": "Prune", + "hex": "#701C1C" + }, + { + "name": "Prussian Blue", + "hex": "#003153" + }, + { + "name": "Psychedelic Purple", + "hex": "#DF00FF" + }, + { + "name": "Puce", + "hex": "#CC8899" + }, + { + "name": "Puce Red", + "hex": "#722F37" + }, + { + "name": "Pullman Brown (UPS Brown)", + "hex": "#644117" + }, + { + "name": "Pullman Green", + "hex": "#3B331C" + }, + { + "name": "Pumpkin", + "hex": "#FF7518" + }, + { + "name": "Purple (HTML)", + "hex": "#800080" + }, + { + "name": "Purple (Munsell)", + "hex": "#9F00C5" + }, + { + "name": "Purple (X11)", + "hex": "#A020F0" + }, + { + "name": "Purple Heart", + "hex": "#69359C" + }, + { + "name": "Purple Mountain Majesty", + "hex": "#9678B6" + }, + { + "name": "Purple Navy", + "hex": "#4E5180" + }, + { + "name": "Purple Pizzazz", + "hex": "#FE4EDA" + }, + { + "name": "Purple Plum", + "hex": "#9C51B6" + }, + { + "name": "Purple Taupe", + "hex": "#50404D" + }, + { + "name": "Purpureus", + "hex": "#9A4EAE" + }, + { + "name": "Quartz", + "hex": "#51484F" + }, + { + "name": "Queen Blue", + "hex": "#436B95" + }, + { + "name": "Queen Pink", + "hex": "#E8CCD7" + }, + { + "name": "Quick Silver", + "hex": "#A6A6A6" + }, + { + "name": "Quinacridone Magenta", + "hex": "#8E3A59" + }, + { + "name": "Rackley", + "hex": "#5D8AA8" + }, + { + "name": "Radical Red", + "hex": "#FF355E" + }, + { + "name": "Raisin Black", + "hex": "#242124" + }, + { + "name": "Rajah", + "hex": "#FBAB60" + }, + { + "name": "Raspberry", + "hex": "#E30B5D" + }, + { + "name": "Raspberry Glace", + "hex": "#915F6D" + }, + { + "name": "Raspberry Pink", + "hex": "#E25098" + }, + { + "name": "Raspberry Rose", + "hex": "#B3446C" + }, + { + "name": "Raw Sienna", + "hex": "#D68A59" + }, + { + "name": "Raw Umber", + "hex": "#826644" + }, + { + "name": "Razzle Dazzle Rose", + "hex": "#FF33CC" + }, + { + "name": "Razzmatazz", + "hex": "#E3256B" + }, + { + "name": "Razzmic Berry", + "hex": "#8D4E85" + }, + { + "name": "Rebecca Purple", + "hex": "#663399" + }, + { + "name": "Red", + "hex": "#FF0000" + }, + { + "name": "Red (Crayola)", + "hex": "#EE204D" + }, + { + "name": "Red (Munsell)", + "hex": "#F2003C" + }, + { + "name": "Red (NCS)", + "hex": "#C40233" + }, + { + "name": "Red (Pantone)", + "hex": "#ED2939" + }, + { + "name": "Red (Pigment)", + "hex": "#ED1C24" + }, + { + "name": "Red (RYB)", + "hex": "#FE2712" + }, + { + "name": "Red-Brown", + "hex": "#A52A2A" + }, + { + "name": "Red Devil", + "hex": "#860111" + }, + { + "name": "Red-Orange", + "hex": "#FF5349" + }, + { + "name": "Red-Purple", + "hex": "#E40078" + }, + { + "name": "Red Salsa", + "hex": "#FD3A4A" + }, + { + "name": "Red-Violet", + "hex": "#C71585" + }, + { + "name": "Redwood", + "hex": "#A45A52" + }, + { + "name": "Regalia", + "hex": "#522D80" + }, + { + "name": "Registration Black", + "hex": "#000000" + }, + { + "name": "Resolution Blue", + "hex": "#002387" + }, + { + "name": "Rhythm", + "hex": "#777696" + }, + { + "name": "Rich Black", + "hex": "#004040" + }, + { + "name": "Rich Black (FOGRA29)", + "hex": "#010B13" + }, + { + "name": "Rich Black (FOGRA39)", + "hex": "#010203" + }, + { + "name": "Rich Brilliant Lavender", + "hex": "#F1A7FE" + }, + { + "name": "Rich Carmine", + "hex": "#D70040" + }, + { + "name": "Rich Electric Blue", + "hex": "#0892D0" + }, + { + "name": "Rich Lavender", + "hex": "#A76BCF" + }, + { + "name": "Rich Lilac", + "hex": "#B666D2" + }, + { + "name": "Rich Maroon", + "hex": "#B03060" + }, + { + "name": "Rifle Green", + "hex": "#444C38" + }, + { + "name": "Roast Coffee", + "hex": "#704241" + }, + { + "name": "Robin Egg Blue", + "hex": "#00CCCC" + }, + { + "name": "Rocket Metallic", + "hex": "#8A7F80" + }, + { + "name": "Roman Silver", + "hex": "#838996" + }, + { + "name": "Rose", + "hex": "#FF007F" + }, + { + "name": "Rose Bonbon", + "hex": "#F9429E" + }, + { + "name": "Rose Dust", + "hex": "#9E5E6F" + }, + { + "name": "Rose Ebony", + "hex": "#674846" + }, + { + "name": "Rose Gold", + "hex": "#B76E79" + }, + { + "name": "Rose Madder", + "hex": "#E32636" + }, + { + "name": "Rose Pink", + "hex": "#FF66CC" + }, + { + "name": "Rose Quartz", + "hex": "#AA98A9" + }, + { + "name": "Rose Red", + "hex": "#C21E56" + }, + { + "name": "Rose Taupe", + "hex": "#905D5D" + }, + { + "name": "Rose Vale", + "hex": "#AB4E52" + }, + { + "name": "Rosewood", + "hex": "#65000B" + }, + { + "name": "Rosso Corsa", + "hex": "#D40000" + }, + { + "name": "Rosy Brown", + "hex": "#BC8F8F" + }, + { + "name": "Royal Azure", + "hex": "#0038A8" + }, + { + "name": "Royal Blue", + "hex": "#002366" + }, + { + "name": "Royal Blue", + "hex": "#4169E1" + }, + { + "name": "Royal Fuchsia", + "hex": "#CA2C92" + }, + { + "name": "Royal Purple", + "hex": "#7851A9" + }, + { + "name": "Royal Yellow", + "hex": "#FADA5E" + }, + { + "name": "Ruber", + "hex": "#CE4676" + }, + { + "name": "Rubine Red", + "hex": "#D10056" + }, + { + "name": "Ruby", + "hex": "#E0115F" + }, + { + "name": "Ruby Red", + "hex": "#9B111E" + }, + { + "name": "Ruddy", + "hex": "#FF0028" + }, + { + "name": "Ruddy Brown", + "hex": "#BB6528" + }, + { + "name": "Ruddy Pink", + "hex": "#E18E96" + }, + { + "name": "Rufous", + "hex": "#A81C07" + }, + { + "name": "Russet", + "hex": "#80461B" + }, + { + "name": "Russian Green", + "hex": "#679267" + }, + { + "name": "Russian Violet", + "hex": "#32174D" + }, + { + "name": "Rust", + "hex": "#B7410E" + }, + { + "name": "Rusty Red", + "hex": "#DA2C43" + }, + { + "name": "Sacramento State Green", + "hex": "#00563F" + }, + { + "name": "Saddle Brown", + "hex": "#8B4513" + }, + { + "name": "Safety Orange", + "hex": "#FF7800" + }, + { + "name": "Safety Orange (Blaze Orange)", + "hex": "#FF6700" + }, + { + "name": "Safety Yellow", + "hex": "#EED202" + }, + { + "name": "Saffron", + "hex": "#F4C430" + }, + { + "name": "Sage", + "hex": "#BCB88A" + }, + { + "name": "St. Patrick's Blue", + "hex": "#23297A" + }, + { + "name": "Salmon", + "hex": "#FA8072" + }, + { + "name": "Salmon Pink", + "hex": "#FF91A4" + }, + { + "name": "Sand", + "hex": "#C2B280" + }, + { + "name": "Sand Dune", + "hex": "#967117" + }, + { + "name": "Sandstorm", + "hex": "#ECD540" + }, + { + "name": "Sandy Brown", + "hex": "#F4A460" + }, + { + "name": "Sandy Tan", + "hex": "#FDD9B5" + }, + { + "name": "Sandy Taupe", + "hex": "#967117" + }, + { + "name": "Sangria", + "hex": "#92000A" + }, + { + "name": "Sap Green", + "hex": "#507D2A" + }, + { + "name": "Sapphire", + "hex": "#0F52BA" + }, + { + "name": "Sapphire Blue", + "hex": "#0067A5" + }, + { + "name": "Sasquatch Socks", + "hex": "#FF4681" + }, + { + "name": "Satin Sheen Gold", + "hex": "#CBA135" + }, + { + "name": "Scarlet", + "hex": "#FF2400" + }, + { + "name": "Scarlet", + "hex": "#FD0E35" + }, + { + "name": "Schauss Pink", + "hex": "#FF91AF" + }, + { + "name": "School Bus Yellow", + "hex": "#FFD800" + }, + { + "name": "Screamin' Green", + "hex": "#66FF66" + }, + { + "name": "Sea Blue", + "hex": "#006994" + }, + { + "name": "Sea Foam Green", + "hex": "#9FE2BF" + }, + { + "name": "Sea Green", + "hex": "#2E8B57" + }, + { + "name": "Sea Serpent", + "hex": "#4BC7CF" + }, + { + "name": "Seal Brown", + "hex": "#59260B" + }, + { + "name": "Seashell", + "hex": "#FFF5EE" + }, + { + "name": "Selective Yellow", + "hex": "#FFBA00" + }, + { + "name": "Sepia", + "hex": "#704214" + }, + { + "name": "Shadow", + "hex": "#8A795D" + }, + { + "name": "Shadow Blue", + "hex": "#778BA5" + }, + { + "name": "Shampoo", + "hex": "#FFCFF1" + }, + { + "name": "Shamrock Green", + "hex": "#009E60" + }, + { + "name": "Sheen Green", + "hex": "#8FD400" + }, + { + "name": "Shimmering Blush", + "hex": "#D98695" + }, + { + "name": "Shiny Shamrock", + "hex": "#5FA778" + }, + { + "name": "Shocking Pink", + "hex": "#FC0FC0" + }, + { + "name": "Shocking Pink (Crayola)", + "hex": "#FF6FFF" + }, + { + "name": "Sienna", + "hex": "#882D17" + }, + { + "name": "Silver", + "hex": "#C0C0C0" + }, + { + "name": "Silver Chalice", + "hex": "#ACACAC" + }, + { + "name": "Silver Lake Blue", + "hex": "#5D89BA" + }, + { + "name": "Silver Pink", + "hex": "#C4AEAD" + }, + { + "name": "Silver Sand", + "hex": "#BFC1C2" + }, + { + "name": "Sinopia", + "hex": "#CB410B" + }, + { + "name": "Sizzling Red", + "hex": "#FF3855" + }, + { + "name": "Sizzling Sunrise", + "hex": "#FFDB00" + }, + { + "name": "Skobeloff", + "hex": "#007474" + }, + { + "name": "Sky Blue", + "hex": "#87CEEB" + }, + { + "name": "Sky Magenta", + "hex": "#CF71AF" + }, + { + "name": "Slate Blue", + "hex": "#6A5ACD" + }, + { + "name": "Slate Gray", + "hex": "#708090" + }, + { + "name": "Smalt (Dark Powder Blue)", + "hex": "#003399" + }, + { + "name": "Slimy Green", + "hex": "#299617" + }, + { + "name": "Smashed Pumpkin", + "hex": "#FF6D3A" + }, + { + "name": "Smitten", + "hex": "#C84186" + }, + { + "name": "Smoke", + "hex": "#738276" + }, + { + "name": "Smokey Topaz", + "hex": "#832A0D" + }, + { + "name": "Smoky Black", + "hex": "#100C08" + }, + { + "name": "Smoky Topaz", + "hex": "#933D41" + }, + { + "name": "Snow", + "hex": "#FFFAFA" + }, + { + "name": "Soap", + "hex": "#CEC8EF" + }, + { + "name": "Solid Pink", + "hex": "#893843" + }, + { + "name": "Sonic Silver", + "hex": "#757575" + }, + { + "name": "Spartan Crimson", + "hex": "#9E1316" + }, + { + "name": "Space Cadet", + "hex": "#1D2951" + }, + { + "name": "Spanish Bistre", + "hex": "#807532" + }, + { + "name": "Spanish Blue", + "hex": "#0070B8" + }, + { + "name": "Spanish Carmine", + "hex": "#D10047" + }, + { + "name": "Spanish Crimson", + "hex": "#E51A4C" + }, + { + "name": "Spanish Gray", + "hex": "#989898" + }, + { + "name": "Spanish Green", + "hex": "#009150" + }, + { + "name": "Spanish Orange", + "hex": "#E86100" + }, + { + "name": "Spanish Pink", + "hex": "#F7BFBE" + }, + { + "name": "Spanish Red", + "hex": "#E60026" + }, + { + "name": "Spanish Sky Blue", + "hex": "#00FFFF" + }, + { + "name": "Spanish Violet", + "hex": "#4C2882" + }, + { + "name": "Spanish Viridian", + "hex": "#007F5C" + }, + { + "name": "Spicy Mix", + "hex": "#8B5f4D" + }, + { + "name": "Spiro Disco Ball", + "hex": "#0FC0FC" + }, + { + "name": "Spring Bud", + "hex": "#A7FC00" + }, + { + "name": "Spring Frost", + "hex": "#87FF2A" + }, + { + "name": "Spring Green", + "hex": "#00FF7F" + }, + { + "name": "Star Command Blue", + "hex": "#007BB8" + }, + { + "name": "Steel Blue", + "hex": "#4682B4" + }, + { + "name": "Steel Pink", + "hex": "#CC33CC" + }, + { + "name": "Steel Teal", + "hex": "#5F8A8B" + }, + { + "name": "Stil De Grain Yellow", + "hex": "#FADA5E" + }, + { + "name": "Stizza", + "hex": "#990000" + }, + { + "name": "Stormcloud", + "hex": "#4F666A" + }, + { + "name": "Straw", + "hex": "#E4D96F" + }, + { + "name": "Strawberry", + "hex": "#FC5A8D" + }, + { + "name": "Sugar Plum", + "hex": "#914E75" + }, + { + "name": "Sunburnt Cyclops", + "hex": "#FF404C" + }, + { + "name": "Sunglow", + "hex": "#FFCC33" + }, + { + "name": "Sunny", + "hex": "#F2F27A" + }, + { + "name": "Sunray", + "hex": "#E3AB57" + }, + { + "name": "Sunset", + "hex": "#FAD6A5" + }, + { + "name": "Sunset Orange", + "hex": "#FD5E53" + }, + { + "name": "Super Pink", + "hex": "#CF6BA9" + }, + { + "name": "Sweet Brown", + "hex": "#A83731" + }, + { + "name": "Tan", + "hex": "#D2B48C" + }, + { + "name": "Tangelo", + "hex": "#F94D00" + }, + { + "name": "Tangerine", + "hex": "#F28500" + }, + { + "name": "Tangerine Yellow", + "hex": "#FFCC00" + }, + { + "name": "Tango Pink", + "hex": "#E4717A" + }, + { + "name": "Tart Orange", + "hex": "#FB4D46" + }, + { + "name": "Taupe", + "hex": "#483C32" + }, + { + "name": "Taupe Gray", + "hex": "#8B8589" + }, + { + "name": "Tea Green", + "hex": "#D0F0C0" + }, + { + "name": "Tea Rose", + "hex": "#F88379" + }, + { + "name": "Tea Rose", + "hex": "#F4C2C2" + }, + { + "name": "Teal", + "hex": "#008080" + }, + { + "name": "Teal Blue", + "hex": "#367588" + }, + { + "name": "Teal Deer", + "hex": "#99E6B3" + }, + { + "name": "Teal Green", + "hex": "#00827F" + }, + { + "name": "Telemagenta", + "hex": "#CF3476" + }, + { + "name": "Tenne (Tawny)", + "hex": "#CD5700" + }, + { + "name": "Terra Cotta", + "hex": "#E2725B" + }, + { + "name": "Thistle", + "hex": "#D8BFD8" + }, + { + "name": "Thulian Pink", + "hex": "#DE6FA1" + }, + { + "name": "Tickle Me Pink", + "hex": "#FC89AC" + }, + { + "name": "Tiffany Blue", + "hex": "#0ABAB5" + }, + { + "name": "Tiger's Eye", + "hex": "#E08D3C" + }, + { + "name": "Timberwolf", + "hex": "#DBD7D2" + }, + { + "name": "Titanium Yellow", + "hex": "#EEE600" + }, + { + "name": "Tomato", + "hex": "#FF6347" + }, + { + "name": "Toolbox", + "hex": "#746CC0" + }, + { + "name": "Topaz", + "hex": "#FFC87C" + }, + { + "name": "Tractor Red", + "hex": "#FD0E35" + }, + { + "name": "Trolley Grey", + "hex": "#808080" + }, + { + "name": "Tropical Rain Forest", + "hex": "#00755E" + }, + { + "name": "Tropical Violet", + "hex": "#CDA4DE" + }, + { + "name": "True Blue", + "hex": "#0073CF" + }, + { + "name": "Tufts Blue", + "hex": "#3E8EDE" + }, + { + "name": "Tulip", + "hex": "#FF878D" + }, + { + "name": "Tumbleweed", + "hex": "#DEAA88" + }, + { + "name": "Turkish Rose", + "hex": "#B57281" + }, + { + "name": "Turquoise", + "hex": "#40E0D0" + }, + { + "name": "Turquoise Blue", + "hex": "#00FFEF" + }, + { + "name": "Turquoise Green", + "hex": "#A0D6B4" + }, + { + "name": "Turquoise Surf", + "hex": "#00C5CD" + }, + { + "name": "Turtle Green", + "hex": "#8A9A5B" + }, + { + "name": "Tuscan", + "hex": "#FAD6A5" + }, + { + "name": "Tuscan Brown", + "hex": "#6F4E37" + }, + { + "name": "Tuscan Red", + "hex": "#7C4848" + }, + { + "name": "Tuscan Tan", + "hex": "#A67B5B" + }, + { + "name": "Tuscany", + "hex": "#C09999" + }, + { + "name": "Twilight Lavender", + "hex": "#8A496B" + }, + { + "name": "Tyrian Purple", + "hex": "#66023C" + }, + { + "name": "UA Blue", + "hex": "#0033AA" + }, + { + "name": "UA Red", + "hex": "#D9004C" + }, + { + "name": "Ube", + "hex": "#8878C3" + }, + { + "name": "UCLA Blue", + "hex": "#536895" + }, + { + "name": "UCLA Gold", + "hex": "#FFB300" + }, + { + "name": "UFO Green", + "hex": "#3CD070" + }, + { + "name": "Ultramarine", + "hex": "#3F00FF" + }, + { + "name": "Ultramarine Blue", + "hex": "#4166F5" + }, + { + "name": "Ultra Pink", + "hex": "#FF6FFF" + }, + { + "name": "Ultra Red", + "hex": "#FC6C85" + }, + { + "name": "Umber", + "hex": "#635147" + }, + { + "name": "Unbleached Silk", + "hex": "#FFDDCA" + }, + { + "name": "United Nations Blue", + "hex": "#5B92E5" + }, + { + "name": "University Of California Gold", + "hex": "#B78727" + }, + { + "name": "Unmellow Yellow", + "hex": "#FFFF66" + }, + { + "name": "UP Forest Green", + "hex": "#014421" + }, + { + "name": "UP Maroon", + "hex": "#7B1113" + }, + { + "name": "Upsdell Red", + "hex": "#AE2029" + }, + { + "name": "Urobilin", + "hex": "#E1AD21" + }, + { + "name": "USAFA Blue", + "hex": "#004F98" + }, + { + "name": "USC Cardinal", + "hex": "#990000" + }, + { + "name": "USC Gold", + "hex": "#FFCC00" + }, + { + "name": "University Of Tennessee Orange", + "hex": "#F77F00" + }, + { + "name": "Utah Crimson", + "hex": "#D3003F" + }, + { + "name": "Van Dyke Brown", + "hex": "#664228" + }, + { + "name": "Vanilla", + "hex": "#F3E5AB" + }, + { + "name": "Vanilla Ice", + "hex": "#F38FA9" + }, + { + "name": "Vegas Gold", + "hex": "#C5B358" + }, + { + "name": "Venetian Red", + "hex": "#C80815" + }, + { + "name": "Verdigris", + "hex": "#43B3AE" + }, + { + "name": "Vermilion", + "hex": "#E34234" + }, + { + "name": "Vermilion", + "hex": "#D9381E" + }, + { + "name": "Veronica", + "hex": "#A020F0" + }, + { + "name": "Very Light Azure", + "hex": "#74BBFB" + }, + { + "name": "Very Light Blue", + "hex": "#6666FF" + }, + { + "name": "Very Light Malachite Green", + "hex": "#64E986" + }, + { + "name": "Very Light Tangelo", + "hex": "#FFB077" + }, + { + "name": "Very Pale Orange", + "hex": "#FFDFBF" + }, + { + "name": "Very Pale Yellow", + "hex": "#FFFFBF" + }, + { + "name": "Violet", + "hex": "#8F00FF" + }, + { + "name": "Violet (Color Wheel)", + "hex": "#7F00FF" + }, + { + "name": "Violet (RYB)", + "hex": "#8601AF" + }, + { + "name": "Violet (Web)", + "hex": "#EE82EE" + }, + { + "name": "Violet-Blue", + "hex": "#324AB2" + }, + { + "name": "Violet-Red", + "hex": "#F75394" + }, + { + "name": "Viridian", + "hex": "#40826D" + }, + { + "name": "Viridian Green", + "hex": "#009698" + }, + { + "name": "Vista Blue", + "hex": "#7C9ED9" + }, + { + "name": "Vivid Amber", + "hex": "#CC9900" + }, + { + "name": "Vivid Auburn", + "hex": "#922724" + }, + { + "name": "Vivid Burgundy", + "hex": "#9F1D35" + }, + { + "name": "Vivid Cerise", + "hex": "#DA1D81" + }, + { + "name": "Vivid Cerulean", + "hex": "#00AAEE" + }, + { + "name": "Vivid Crimson", + "hex": "#CC0033" + }, + { + "name": "Vivid Gamboge", + "hex": "#FF9900" + }, + { + "name": "Vivid Lime Green", + "hex": "#A6D608" + }, + { + "name": "Vivid Malachite", + "hex": "#00CC33" + }, + { + "name": "Vivid Mulberry", + "hex": "#B80CE3" + }, + { + "name": "Vivid Orange", + "hex": "#FF5F00" + }, + { + "name": "Vivid Orange Peel", + "hex": "#FFA000" + }, + { + "name": "Vivid Orchid", + "hex": "#CC00FF" + }, + { + "name": "Vivid Raspberry", + "hex": "#FF006C" + }, + { + "name": "Vivid Red", + "hex": "#F70D1A" + }, + { + "name": "Vivid Red-Tangelo", + "hex": "#DF6124" + }, + { + "name": "Vivid Sky Blue", + "hex": "#00CCFF" + }, + { + "name": "Vivid Tangelo", + "hex": "#F07427" + }, + { + "name": "Vivid Tangerine", + "hex": "#FFA089" + }, + { + "name": "Vivid Vermilion", + "hex": "#E56024" + }, + { + "name": "Vivid Violet", + "hex": "#9F00FF" + }, + { + "name": "Vivid Yellow", + "hex": "#FFE302" + }, + { + "name": "Volt", + "hex": "#CEFF00" + }, + { + "name": "Wageningen Green", + "hex": "#34B233" + }, + { + "name": "Warm Black", + "hex": "#004242" + }, + { + "name": "Waterspout", + "hex": "#A4F4F9" + }, + { + "name": "Weldon Blue", + "hex": "#7C98AB" + }, + { + "name": "Wenge", + "hex": "#645452" + }, + { + "name": "Wheat", + "hex": "#F5DEB3" + }, + { + "name": "White", + "hex": "#FFFFFF" + }, + { + "name": "White Smoke", + "hex": "#F5F5F5" + }, + { + "name": "Wild Blue Yonder", + "hex": "#A2ADD0" + }, + { + "name": "Wild Orchid", + "hex": "#D470A2" + }, + { + "name": "Wild Strawberry", + "hex": "#FF43A4" + }, + { + "name": "Wild Watermelon", + "hex": "#FC6C85" + }, + { + "name": "Willpower Orange", + "hex": "#FD5800" + }, + { + "name": "Windsor Tan", + "hex": "#A75502" + }, + { + "name": "Wine", + "hex": "#722F37" + }, + { + "name": "Wine Dregs", + "hex": "#673147" + }, + { + "name": "Winter Sky", + "hex": "#FF007C" + }, + { + "name": "Winter Wizard", + "hex": "#A0E6FF" + }, + { + "name": "Wintergreen Dream", + "hex": "#56887D" + }, + { + "name": "Wisteria", + "hex": "#C9A0DC" + }, + { + "name": "Wood Brown", + "hex": "#C19A6B" + }, + { + "name": "Xanadu", + "hex": "#738678" + }, + { + "name": "Yale Blue", + "hex": "#0F4D92" + }, + { + "name": "Yankees Blue", + "hex": "#1C2841" + }, + { + "name": "Yellow", + "hex": "#FFFF00" + }, + { + "name": "Yellow (Crayola)", + "hex": "#FCE883" + }, + { + "name": "Yellow (Munsell)", + "hex": "#EFCC00" + }, + { + "name": "Yellow (NCS)", + "hex": "#FFD300" + }, + { + "name": "Yellow (Pantone)", + "hex": "#FEDF00" + }, + { + "name": "Yellow (Process)", + "hex": "#FFEF00" + }, + { + "name": "Yellow (RYB)", + "hex": "#FEFE33" + }, + { + "name": "Yellow-Green", + "hex": "#9ACD32" + }, + { + "name": "Yellow Orange", + "hex": "#FFAE42" + }, + { + "name": "Yellow Rose", + "hex": "#FFF000" + }, + { + "name": "Yellow Sunshine", + "hex": "#FFF700" + }, + { + "name": "Zaffre", + "hex": "#0014A8" + }, + { + "name": "Zinnwaldite Brown", + "hex": "#2C1608" + }, + { + "name": "Zomp", + "hex": "#39A78E" + } +]` diff --git a/core/docker-compose.yml b/core/docker-compose.yml new file mode 100644 index 00000000..c54be4f8 --- /dev/null +++ b/core/docker-compose.yml @@ -0,0 +1,14 @@ +version: '3.3' +services: + mongo: + image: mongo:latest + container_name: mongo + restart: always + ports: + - "27017:27017" + redis: + image: redis:latest + container_name: redis + restart: always + ports: + - "6379:6379" diff --git a/core/docs/.gitignore b/core/docs/.gitignore new file mode 100644 index 00000000..2ac2692a --- /dev/null +++ b/core/docs/.gitignore @@ -0,0 +1,6 @@ +.idea +node_modules +dist +build +tmp +yarn.lock diff --git a/core/docs/api/index.html b/core/docs/api/index.html new file mode 100644 index 00000000..bb21255e --- /dev/null +++ b/core/docs/api/index.html @@ -0,0 +1,10 @@ + + +
+ + + + +
+
|
+ |||||||||||
{{ $line }}
+ {{ end }} +{{ end }} +{{ if (ne .Email.Body.FreeMarkdown "") }} + {{ .Email.Body.FreeMarkdown.ToHTML }} +{{ else }} + {{ with .Email.Body.Dictionary }} +| {{ $entry.Key }} | + {{ end }} +
|---|
| + {{ $cell.Value }} + | + {{ end }} +
{{ $action.Instructions }} {{ $action.Button.Link }}
+ {{ end }} + {{ end }} +{{ end }} +{{ with .Email.Body.Outros }} + {{ range $line := . }} +{{ $line }}
+ {{ end }} +{{ end }} +
{{.Email.Body.Signature}},
{{.Hermes.Product.Name}} - {{.Hermes.Product.Link}}
{{.Hermes.Product.Copyright}}
+` +} + +func (dt *MailThemeFlat) GetStyle() string { + return ` + +` +} diff --git a/core/notification/mobile.go b/core/notification/mobile.go new file mode 100644 index 00000000..cd36641b --- /dev/null +++ b/core/notification/mobile.go @@ -0,0 +1,62 @@ +package notification + +import ( + "errors" + "github.com/crawlab-team/go-trace" + "github.com/imroc/req" + "strings" +) + +type ResBody struct { + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` +} + +func SendMobileNotification(webhook string, title string, content string) error { + // request header + header := req.Header{ + "Content-Type": "application/json; charset=utf-8", + } + + // request data + data := req.Param{ + "msgtype": "markdown", + "markdown": req.Param{ + "title": title, + "text": content, + "content": content, + }, + "at": req.Param{ + "atMobiles": []string{}, + "isAtAll": false, + }, + "text": content, + } + if strings.Contains(strings.ToLower(webhook), "feishu") { + data = req.Param{ + "msg_type": "text", + "content": req.Param{ + "text": content, + }, + } + } + + // perform request + res, err := req.Post(webhook, header, req.BodyJSON(&data)) + if err != nil { + return trace.TraceError(err) + } + + // parse response + var resBody ResBody + if err := res.ToJSON(&resBody); err != nil { + return trace.TraceError(err) + } + + // validate response code + if resBody.ErrCode != 0 { + return errors.New(resBody.ErrMsg) + } + + return nil +} diff --git a/core/notification/models.go b/core/notification/models.go new file mode 100644 index 00000000..efb55448 --- /dev/null +++ b/core/notification/models.go @@ -0,0 +1,32 @@ +package notification + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type NotificationSetting struct { + Id primitive.ObjectID `json:"_id" bson:"_id"` + Type string `json:"type" bson:"type"` + Name string `json:"name" bson:"name"` + Description string `json:"description" bson:"description"` + Enabled bool `json:"enabled" bson:"enabled"` + Global bool `json:"global" bson:"global"` + Title string `json:"title,omitempty" bson:"title,omitempty"` + Template string `json:"template,omitempty" bson:"template,omitempty"` + TaskTrigger string `json:"task_trigger" bson:"task_trigger"` + Mail NotificationSettingMail `json:"mail,omitempty" bson:"mail,omitempty"` + Mobile NotificationSettingMobile `json:"mobile,omitempty" bson:"mobile,omitempty"` +} + +type NotificationSettingMail struct { + Server string `json:"server" bson:"server"` + Port string `json:"port,omitempty" bson:"port,omitempty"` + User string `json:"user,omitempty" bson:"user,omitempty"` + Password string `json:"password,omitempty" bson:"password,omitempty"` + SenderEmail string `json:"sender_email,omitempty" bson:"sender_email,omitempty"` + SenderIdentity string `json:"sender_identity,omitempty" bson:"sender_identity,omitempty"` + To string `json:"to,omitempty" bson:"to,omitempty"` + Cc string `json:"cc,omitempty" bson:"cc,omitempty"` +} + +type NotificationSettingMobile struct { + Webhook string `json:"webhook" bson:"webhook"` +} diff --git a/core/notification/payload.go b/core/notification/payload.go new file mode 100644 index 00000000..7337ebe0 --- /dev/null +++ b/core/notification/payload.go @@ -0,0 +1,8 @@ +package notification + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type SendPayload struct { + TaskId primitive.ObjectID `json:"task_id"` + Data string `json:"data"` +} diff --git a/core/notification/service.go b/core/notification/service.go new file mode 100644 index 00000000..b2fd8a27 --- /dev/null +++ b/core/notification/service.go @@ -0,0 +1,395 @@ +package notification + +import ( + "github.com/apex/log" + mongo2 "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + parser "github.com/crawlab-team/template-parser" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type Service struct { + col *mongo2.Col // notification settings + modelSvc service.ModelService +} + +func (svc *Service) Init() (err error) { + if !utils.IsPro() { + return nil + } + + return nil +} + +func (svc *Service) Start() (err error) { + // initialize data + if err := svc.initData(); err != nil { + return err + } + + return nil +} + +func (svc *Service) Stop() (err error) { + return nil +} + +func (svc *Service) initData() (err error) { + total, err := svc.col.Count(nil) + if err != nil { + return err + } + if total > 0 { + return nil + } + + // data to initialize + settings := []NotificationSetting{ + { + Id: primitive.NewObjectID(), + Type: TypeMail, + Enabled: true, + Name: "任务通知(邮件)", + Description: "这是默认的邮件通知。您可以使用您自己的设置进行编辑。", + TaskTrigger: constants.NotificationTriggerTaskFinish, + Title: "[Crawlab] 爬虫任务更新: {{$.status}}", + Template: `尊敬的 {{$.user.username}}, + +请查看下面的任务数据。 + +|键|值| +|:-:|:--| +|任务状态|{{$.status}}| +|任务优先级|{{$.priority}}| +|任务模式|{{$.mode}}| +|执行命令|{{$.cmd}}| +|执行参数|{{$.param}}| +|错误信息|{{$.error}}| +|节点|{{$.node.name}}| +|爬虫|{{$.spider.name}}| +|项目|{{$.spider.project.name}}| +|定时任务|{{$.schedule.name}}| +|结果数|{{$.:task_stat.result_count}}| +|等待时间(秒)|{#{{$.:task_stat.wait_duration}}/1000#}| +|运行时间(秒)|{#{{$.:task_stat.runtime_duration}}/1000#}| +|总时间(秒)|{#{{$.:task_stat.total_duration}}/1000#}| +|平均结果数/秒|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}| +`, + Mail: NotificationSettingMail{ + Server: "smtp.163.com", + Port: "465", + To: "{{$.user[create].email}}", + }, + }, + { + Id: primitive.NewObjectID(), + Type: TypeMail, + Enabled: true, + Name: "Task Change (Mail)", + Description: "This is the default mail notification. You can edit it with your own settings", + TaskTrigger: constants.NotificationTriggerTaskFinish, + Title: "[Crawlab] Task Update: {{$.status}}", + Template: `Dear {{$.user.username}}, + +Please find the task data as below. + +|Key|Value| +|:-:|:--| +|Task Status|{{$.status}}| +|Task Priority|{{$.priority}}| +|Task Mode|{{$.mode}}| +|Task Command|{{$.cmd}}| +|Task Params|{{$.param}}| +|Error Message|{{$.error}}| +|Node|{{$.node.name}}| +|Spider|{{$.spider.name}}| +|Project|{{$.spider.project.name}}| +|Schedule|{{$.schedule.name}}| +|Result Count|{{$.:task_stat.result_count}}| +|Wait Duration (sec)|{#{{$.:task_stat.wait_duration}}/1000#}| +|Runtime Duration (sec)|{#{{$.:task_stat.runtime_duration}}/1000#}| +|Total Duration (sec)|{#{{$.:task_stat.total_duration}}/1000#}| +|Avg Results / Sec|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}| +`, + Mail: NotificationSettingMail{ + Server: "smtp.163.com", + Port: "465", + To: "{{$.user[create].email}}", + }, + }, + { + Id: primitive.NewObjectID(), + Type: TypeMobile, + Enabled: true, + Name: "任务通知(移动端)", + Description: "这是默认的手机通知。您可以使用您自己的设置进行编辑。", + TaskTrigger: constants.NotificationTriggerTaskFinish, + Title: "[Crawlab] 任务更新: {{$.status}}", + Template: `尊敬的 {{$.user.username}}, + +请查看下面的任务数据。 + +- **任务状态**: {{$.status}} +- **任务优先级**: {{$.priority}} +- **任务模式**: {{$.mode}} +- **执行命令**: {{$.cmd}} +- **执行参数**: {{$.param}} +- **错误信息**: {{$.error}} +- **节点**: {{$.node.name}} +- **爬虫**: {{$.spider.name}} +- **项目**: {{$.spider.project.name}} +- **定时任务**: {{$.schedule.name}} +- **结果数**: {{$.:task_stat.result_count}} +- **等待时间(秒)**: {#{{$.:task_stat.wait_duration}}/1000#} +- **运行时间(秒)**: {#{{$.:task_stat.runtime_duration}}/1000#} +- **总时间(秒)**: {#{{$.:task_stat.total_duration}}/1000#} +- **平均结果数/秒**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`, + Mobile: NotificationSettingMobile{}, + }, + { + Id: primitive.NewObjectID(), + Type: TypeMobile, + Enabled: true, + Name: "Task Change (Mobile)", + Description: "This is the default mobile notification. You can edit it with your own settings", + TaskTrigger: constants.NotificationTriggerTaskError, + Title: "[Crawlab] Task Update: {{$.status}}", + Template: `Dear {{$.user.username}}, + +Please find the task data as below. + +- **Task Status**: {{$.status}} +- **Task Priority**: {{$.priority}} +- **Task Mode**: {{$.mode}} +- **Task Command**: {{$.cmd}} +- **Task Params**: {{$.param}} +- **Error Message**: {{$.error}} +- **Node**: {{$.node.name}} +- **Spider**: {{$.spider.name}} +- **Project**: {{$.spider.project.name}} +- **Schedule**: {{$.schedule.name}} +- **Result Count**: {{$.:task_stat.result_count}} +- **Wait Duration (sec)**: {#{{$.:task_stat.wait_duration}}/1000#} +- **Runtime Duration (sec)**: {#{{$.:task_stat.runtime_duration}}/1000#} +- **Total Duration (sec)**: {#{{$.:task_stat.total_duration}}/1000#} +- **Avg Results / Sec**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`, + Mobile: NotificationSettingMobile{}, + }, + } + var data []interface{} + for _, s := range settings { + data = append(data, s) + } + _, err = svc.col.InsertMany(data) + if err != nil { + return err + } + return nil +} + +func (svc *Service) Send(s NotificationSetting, entity bson.M) (err error) { + switch s.Type { + case TypeMail: + return svc.SendMail(s, entity) + case TypeMobile: + return svc.SendMobile(s, entity) + } + return nil +} + +func (svc *Service) SendMail(s NotificationSetting, entity bson.M) (err error) { + // to + to, err := parser.Parse(s.Mail.To, entity) + if err != nil { + log.Warnf("parsing 'to' error: %v", err) + } + if to == "" { + return nil + } + + // cc + cc, err := parser.Parse(s.Mail.Cc, entity) + if err != nil { + log.Warnf("parsing 'cc' error: %v", err) + } + + // title + title, err := parser.Parse(s.Title, entity) + if err != nil { + log.Warnf("parsing 'title' error: %v", err) + } + + // content + content, err := parser.Parse(s.Template, entity) + if err != nil { + log.Warnf("parsing 'content' error: %v", err) + } + + // send mail + if err := SendMail(&models.NotificationSettingMail{ + Server: s.Mail.Server, + Port: s.Mail.Port, + User: s.Mail.User, + Password: s.Mail.Password, + SenderEmail: s.Mail.SenderEmail, + SenderIdentity: s.Mail.SenderIdentity, + To: s.Mail.To, + Cc: s.Mail.Cc, + }, to, cc, title, content); err != nil { + return err + } + + return nil +} + +func (svc *Service) SendMobile(s NotificationSetting, entity bson.M) (err error) { + // webhook + webhook, err := parser.Parse(s.Mobile.Webhook, entity) + if err != nil { + log.Warnf("parsing 'webhook' error: %v", err) + } + if webhook == "" { + return nil + } + + // title + title, err := parser.Parse(s.Title, entity) + if err != nil { + log.Warnf("parsing 'title' error: %v", err) + } + + // content + content, err := parser.Parse(s.Template, entity) + if err != nil { + log.Warnf("parsing 'content' error: %v", err) + } + + // send + if err := SendMobileNotification(webhook, title, content); err != nil { + return err + } + + return nil +} + +func (svc *Service) GetSettingList(query bson.M, pagination *entity.Pagination, sort bson.D) (res []NotificationSetting, total int, err error) { + // options + var options *mongo2.FindOptions + if pagination != nil || sort != nil { + options = new(mongo2.FindOptions) + if pagination != nil { + options.Skip = pagination.Size * (pagination.Page - 1) + options.Limit = pagination.Size + } + if sort != nil { + options.Sort = sort + } + } + + // get list + var list []NotificationSetting + if err := svc.col.Find(query, options).All(&list); err != nil { + if err.Error() == mongo.ErrNoDocuments.Error() { + return nil, 0, nil + } else { + return nil, 0, err + } + } + + // total count + total, err = svc.col.Count(query) + if err != nil { + return nil, 0, err + } + + return list, total, nil +} + +func (svc *Service) GetSetting(id primitive.ObjectID) (res *NotificationSetting, err error) { + var s NotificationSetting + if err := svc.col.FindId(id).One(&s); err != nil { + return nil, err + } + return &s, nil +} + +func (svc *Service) PosSetting(s *NotificationSetting) (err error) { + s.Id = primitive.NewObjectID() + if _, err := svc.col.Insert(s); err != nil { + return err + } + return nil +} + +func (svc *Service) PutSetting(id primitive.ObjectID, s NotificationSetting) (err error) { + if err := svc.col.ReplaceId(id, s); err != nil { + return err + } + + return nil +} + +func (svc *Service) DeleteSetting(id primitive.ObjectID) (err error) { + if err := svc.col.DeleteId(id); err != nil { + return err + } + + return nil +} + +func (svc *Service) EnableSetting(id primitive.ObjectID) (err error) { + return svc._toggleSettingFunc(true)(id) +} + +func (svc *Service) DisableSetting(id primitive.ObjectID) (err error) { + return svc._toggleSettingFunc(false)(id) +} + +func (svc *Service) _toggleSettingFunc(value bool) func(id primitive.ObjectID) error { + return func(id primitive.ObjectID) (err error) { + var s NotificationSetting + if err := svc.col.FindId(id).One(&s); err != nil { + return err + } + s.Enabled = value + if err := svc.col.ReplaceId(id, s); err != nil { + return err + } + return nil + } +} + +func NewService() *Service { + // service + svc := &Service{ + col: mongo2.GetMongoCol(SettingsColName), + } + + // model service + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + svc.modelSvc = modelSvc + + if err := svc.Init(); err != nil { + panic(err) + } + + return svc +} + +var _service *Service + +func GetService() *Service { + if _service == nil { + _service = NewService() + } + return _service +} diff --git a/core/notification/service_test.go b/core/notification/service_test.go new file mode 100644 index 00000000..20e09478 --- /dev/null +++ b/core/notification/service_test.go @@ -0,0 +1,19 @@ +package notification + +import ( + "net/http" + "testing" + "time" +) + +func TestService_sendMobile(t *testing.T) { + T.Setup(t) + e := T.NewExpect(t) + time.Sleep(1 * time.Second) + + data := map[string]interface{}{ + "task_id": T.TestTask.GetId().Hex(), + } + e.POST("/send/mobile").WithJSON(data). + Expect().Status(http.StatusOK) +} diff --git a/core/notification/service_v2.go b/core/notification/service_v2.go new file mode 100644 index 00000000..23ade59b --- /dev/null +++ b/core/notification/service_v2.go @@ -0,0 +1,362 @@ +package notification + +import ( + "github.com/apex/log" + mongo2 "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + parser "github.com/crawlab-team/template-parser" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type ServiceV2 struct { +} + +func (svc *ServiceV2) Start() (err error) { + // initialize data + if err := svc.initData(); err != nil { + return err + } + + return nil +} + +func (svc *ServiceV2) Stop() (err error) { + return nil +} + +func (svc *ServiceV2) initData() (err error) { + total, err := service.NewModelServiceV2[models.NotificationSettingV2]().Count(nil) + if err != nil { + return err + } + if total > 0 { + return nil + } + + // data to initialize + settings := []models.NotificationSettingV2{ + { + Id: primitive.NewObjectID(), + Type: TypeMail, + Enabled: true, + Name: "任务通知(邮件)", + Description: "这是默认的邮件通知。您可以使用您自己的设置进行编辑。", + TaskTrigger: constants.NotificationTriggerTaskFinish, + Title: "[Crawlab] 爬虫任务更新: {{$.status}}", + Template: `尊敬的 {{$.user.username}}, + +请查看下面的任务数据。 + +|键|值| +|:-:|:--| +|任务状态|{{$.status}}| +|任务优先级|{{$.priority}}| +|任务模式|{{$.mode}}| +|执行命令|{{$.cmd}}| +|执行参数|{{$.param}}| +|错误信息|{{$.error}}| +|节点|{{$.node.name}}| +|爬虫|{{$.spider.name}}| +|项目|{{$.spider.project.name}}| +|定时任务|{{$.schedule.name}}| +|结果数|{{$.:task_stat.result_count}}| +|等待时间(秒)|{#{{$.:task_stat.wait_duration}}/1000#}| +|运行时间(秒)|{#{{$.:task_stat.runtime_duration}}/1000#}| +|总时间(秒)|{#{{$.:task_stat.total_duration}}/1000#}| +|平均结果数/秒|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}| +`, + Mail: models.NotificationSettingMail{ + Server: "smtp.163.com", + Port: "465", + To: "{{$.user[create].email}}", + }, + }, + { + Id: primitive.NewObjectID(), + Type: TypeMail, + Enabled: true, + Name: "Task Change (Mail)", + Description: "This is the default mail notification. You can edit it with your own settings", + TaskTrigger: constants.NotificationTriggerTaskFinish, + Title: "[Crawlab] Task Update: {{$.status}}", + Template: `Dear {{$.user.username}}, + +Please find the task data as below. + +|Key|Value| +|:-:|:--| +|Task Status|{{$.status}}| +|Task Priority|{{$.priority}}| +|Task Mode|{{$.mode}}| +|Task Command|{{$.cmd}}| +|Task Params|{{$.param}}| +|Error Message|{{$.error}}| +|Node|{{$.node.name}}| +|Spider|{{$.spider.name}}| +|Project|{{$.spider.project.name}}| +|Schedule|{{$.schedule.name}}| +|Result Count|{{$.:task_stat.result_count}}| +|Wait Duration (sec)|{#{{$.:task_stat.wait_duration}}/1000#}| +|Runtime Duration (sec)|{#{{$.:task_stat.runtime_duration}}/1000#}| +|Total Duration (sec)|{#{{$.:task_stat.total_duration}}/1000#}| +|Avg Results / Sec|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}| +`, + Mail: models.NotificationSettingMail{ + Server: "smtp.163.com", + Port: "465", + To: "{{$.user[create].email}}", + }, + }, + { + Id: primitive.NewObjectID(), + Type: TypeMobile, + Enabled: true, + Name: "任务通知(移动端)", + Description: "这是默认的手机通知。您可以使用您自己的设置进行编辑。", + TaskTrigger: constants.NotificationTriggerTaskFinish, + Title: "[Crawlab] 任务更新: {{$.status}}", + Template: `尊敬的 {{$.user.username}}, + +请查看下面的任务数据。 + +- **任务状态**: {{$.status}} +- **任务优先级**: {{$.priority}} +- **任务模式**: {{$.mode}} +- **执行命令**: {{$.cmd}} +- **执行参数**: {{$.param}} +- **错误信息**: {{$.error}} +- **节点**: {{$.node.name}} +- **爬虫**: {{$.spider.name}} +- **项目**: {{$.spider.project.name}} +- **定时任务**: {{$.schedule.name}} +- **结果数**: {{$.:task_stat.result_count}} +- **等待时间(秒)**: {#{{$.:task_stat.wait_duration}}/1000#} +- **运行时间(秒)**: {#{{$.:task_stat.runtime_duration}}/1000#} +- **总时间(秒)**: {#{{$.:task_stat.total_duration}}/1000#} +- **平均结果数/秒**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`, + Mobile: models.NotificationSettingMobile{}, + }, + { + Id: primitive.NewObjectID(), + Type: TypeMobile, + Enabled: true, + Name: "Task Change (Mobile)", + Description: "This is the default mobile notification. You can edit it with your own settings", + TaskTrigger: constants.NotificationTriggerTaskError, + Title: "[Crawlab] Task Update: {{$.status}}", + Template: `Dear {{$.user.username}}, + +Please find the task data as below. + +- **Task Status**: {{$.status}} +- **Task Priority**: {{$.priority}} +- **Task Mode**: {{$.mode}} +- **Task Command**: {{$.cmd}} +- **Task Params**: {{$.param}} +- **Error Message**: {{$.error}} +- **Node**: {{$.node.name}} +- **Spider**: {{$.spider.name}} +- **Project**: {{$.spider.project.name}} +- **Schedule**: {{$.schedule.name}} +- **Result Count**: {{$.:task_stat.result_count}} +- **Wait Duration (sec)**: {#{{$.:task_stat.wait_duration}}/1000#} +- **Runtime Duration (sec)**: {#{{$.:task_stat.runtime_duration}}/1000#} +- **Total Duration (sec)**: {#{{$.:task_stat.total_duration}}/1000#} +- **Avg Results / Sec**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`, + Mobile: models.NotificationSettingMobile{}, + }, + } + _, err = service.NewModelServiceV2[models.NotificationSettingV2]().InsertMany(settings) + if err != nil { + return err + } + return nil +} + +func (svc *ServiceV2) Send(s *models.NotificationSettingV2, entity bson.M) (err error) { + switch s.Type { + case TypeMail: + return svc.SendMail(s, entity) + case TypeMobile: + return svc.SendMobile(s, entity) + } + return nil +} + +func (svc *ServiceV2) SendMail(s *models.NotificationSettingV2, entity bson.M) (err error) { + // to + to, err := parser.Parse(s.Mail.To, entity) + if err != nil { + log.Warnf("parsing 'to' error: %v", err) + } + if to == "" { + return nil + } + + // cc + cc, err := parser.Parse(s.Mail.Cc, entity) + if err != nil { + log.Warnf("parsing 'cc' error: %v", err) + } + + // title + title, err := parser.Parse(s.Title, entity) + if err != nil { + log.Warnf("parsing 'title' error: %v", err) + } + + // content + content, err := parser.Parse(s.Template, entity) + if err != nil { + log.Warnf("parsing 'content' error: %v", err) + } + + // send mail + if err := SendMail(&s.Mail, to, cc, title, content); err != nil { + return err + } + + return nil +} + +func (svc *ServiceV2) SendMobile(s *models.NotificationSettingV2, entity bson.M) (err error) { + // webhook + webhook, err := parser.Parse(s.Mobile.Webhook, entity) + if err != nil { + log.Warnf("parsing 'webhook' error: %v", err) + } + if webhook == "" { + return nil + } + + // title + title, err := parser.Parse(s.Title, entity) + if err != nil { + log.Warnf("parsing 'title' error: %v", err) + } + + // content + content, err := parser.Parse(s.Template, entity) + if err != nil { + log.Warnf("parsing 'content' error: %v", err) + } + + // send + if err := SendMobileNotification(webhook, title, content); err != nil { + return err + } + + return nil +} + +func (svc *ServiceV2) GetSettingList(query bson.M, pagination *entity.Pagination, sort bson.D) (res []models.NotificationSettingV2, total int, err error) { + // options + var options *mongo2.FindOptions + if pagination != nil || sort != nil { + options = new(mongo2.FindOptions) + if pagination != nil { + options.Skip = pagination.Size * (pagination.Page - 1) + options.Limit = pagination.Size + } + if sort != nil { + options.Sort = sort + } + } + + // get list + list, err := service.NewModelServiceV2[models.NotificationSettingV2]().GetMany(query, options) + if err != nil { + if err.Error() == mongo.ErrNoDocuments.Error() { + return nil, 0, nil + } else { + return nil, 0, err + } + } + + // total count + total, err = service.NewModelServiceV2[models.NotificationSettingV2]().Count(query) + if err != nil { + return nil, 0, err + } + + return list, total, nil +} + +func (svc *ServiceV2) GetSetting(id primitive.ObjectID) (res *models.NotificationSettingV2, err error) { + s, err := service.NewModelServiceV2[models.NotificationSettingV2]().GetById(id) + if err != nil { + return nil, err + } + return s, nil +} + +func (svc *ServiceV2) PosSetting(s *models.NotificationSettingV2) (err error) { + s.Id = primitive.NewObjectID() + _, err = service.NewModelServiceV2[models.NotificationSettingV2]().InsertOne(*s) + if err != nil { + return err + } + return nil +} + +func (svc *ServiceV2) PutSetting(id primitive.ObjectID, s models.NotificationSettingV2) (err error) { + err = service.NewModelServiceV2[models.NotificationSettingV2]().ReplaceById(id, s) + if err != nil { + return err + } + + return nil +} + +func (svc *ServiceV2) DeleteSetting(id primitive.ObjectID) (err error) { + err = service.NewModelServiceV2[models.NotificationSettingV2]().DeleteById(id) + if err != nil { + return err + } + + return nil +} + +func (svc *ServiceV2) EnableSetting(id primitive.ObjectID) (err error) { + return svc._toggleSettingFunc(true)(id) +} + +func (svc *ServiceV2) DisableSetting(id primitive.ObjectID) (err error) { + return svc._toggleSettingFunc(false)(id) +} + +func (svc *ServiceV2) _toggleSettingFunc(value bool) func(id primitive.ObjectID) error { + return func(id primitive.ObjectID) (err error) { + s, err := service.NewModelServiceV2[models.NotificationSettingV2]().GetById(id) + if err != nil { + return err + } + s.Enabled = value + err = service.NewModelServiceV2[models.NotificationSettingV2]().ReplaceById(id, *s) + if err != nil { + return err + } + return nil + } +} + +func NewServiceV2() *ServiceV2 { + // service + svc := &ServiceV2{} + + return svc +} + +var _serviceV2 *ServiceV2 + +func GetServiceV2() *ServiceV2 { + if _serviceV2 == nil { + _serviceV2 = NewServiceV2() + } + return _serviceV2 +} diff --git a/core/process/daemon.go b/core/process/daemon.go new file mode 100644 index 00000000..0a5395c4 --- /dev/null +++ b/core/process/daemon.go @@ -0,0 +1,170 @@ +package process + +import ( + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/sys_exec" + "github.com/crawlab-team/go-trace" + "math/rand" + "os/exec" + "time" +) + +const ( + SignalCreate = iota + SignalStart + SignalStopped + SignalError + SignalExited + SignalReachedMaxErrors +) + +type Daemon struct { + // settings + maxErrors int + exitTimeout time.Duration + + // internals + errors int + errMsg string + exitCode int + newCmdFn func() *exec.Cmd + cmd *exec.Cmd + stopped bool + ch chan int +} + +func (d *Daemon) Start() (err error) { + go d.handleSignal() + + for { + // command + d.cmd = d.newCmdFn() + d.ch <- SignalCreate + + // attempt to run + _ = d.cmd.Start() + d.ch <- SignalStart + + if err := d.cmd.Wait(); err != nil { + // stopped + d.ch <- SignalStopped + if d.stopped { + log.Infof("daemon stopped") + return nil + } + + // error + d.ch <- SignalError + d.errMsg = err.Error() + trace.PrintError(err) + } + + // exited + d.ch <- SignalExited + + // exit code + d.exitCode = d.cmd.ProcessState.ExitCode() + + // check exit code + if d.exitCode == 0 { + log.Infof("process exited with code 0") + return + } + + // error message + d.errMsg = errors.ErrorProcessDaemonProcessExited.Error() + + // increment errors + d.errors++ + + // validate if error count exceeds max errors + if d.errors >= d.maxErrors { + log.Infof("reached max errors: %d", d.maxErrors) + d.ch <- SignalReachedMaxErrors + return errors.ErrorProcessReachedMaxErrors + } + + // re-attempt + waitSec := rand.Intn(5) + log.Infof("re-attempt to start process in %d seconds...", waitSec) + time.Sleep(time.Duration(waitSec) * time.Second) + } +} + +func (d *Daemon) Stop() { + d.stopped = true + opts := &sys_exec.KillProcessOptions{ + Timeout: d.exitTimeout, + Force: false, + } + _ = sys_exec.KillProcess(d.cmd, opts) +} + +func (d *Daemon) GetMaxErrors() (maxErrors int) { + return d.maxErrors +} + +func (d *Daemon) SetMaxErrors(maxErrors int) { + d.maxErrors = maxErrors +} + +func (d *Daemon) GetExitTimeout() (timeout time.Duration) { + return d.exitTimeout +} + +func (d *Daemon) SetExitTimeout(timeout time.Duration) { + d.exitTimeout = timeout +} + +func (d *Daemon) GetCmd() (cmd *exec.Cmd) { + return d.cmd +} + +func (d *Daemon) GetCh() (ch chan int) { + return d.ch +} + +func (d *Daemon) handleSignal() { + for { + select { + case signal := <-d.ch: + switch signal { + case SignalCreate: + log.Infof("process created") + case SignalStart: + log.Infof("process started") + case SignalStopped: + log.Infof("process stopped") + case SignalError: + trace.PrintError(errors.NewProcessError(d.errMsg)) + case SignalExited: + log.Infof("process exited") + case SignalReachedMaxErrors: + log.Infof("reached max errors") + return + } + } + } +} + +func NewProcessDaemon(newCmdFn func() *exec.Cmd, opts ...DaemonOption) (d interfaces.ProcessDaemon) { + // daemon + d = &Daemon{ + maxErrors: 5, + exitTimeout: 15 * time.Second, + errors: 0, + errMsg: "", + newCmdFn: newCmdFn, + stopped: false, + ch: make(chan int), + } + + // apply options + for _, opt := range opts { + opt(d) + } + + return d +} diff --git a/core/process/daemon_test.go b/core/process/daemon_test.go new file mode 100644 index 00000000..7734eb59 --- /dev/null +++ b/core/process/daemon_test.go @@ -0,0 +1,21 @@ +package process + +import ( + "github.com/stretchr/testify/require" + "os/exec" + "testing" +) + +func TestDaemon(t *testing.T) { + d := NewProcessDaemon(func() *exec.Cmd { + return exec.Command("echo", "hello") + }) + err := d.Start() + require.Nil(t, err) + + d = NewProcessDaemon(func() *exec.Cmd { + return exec.Command("return", "1") + }) + err = d.Start() + require.NotNil(t, err) +} diff --git a/core/process/manage.go b/core/process/manage.go new file mode 100644 index 00000000..d2b05013 --- /dev/null +++ b/core/process/manage.go @@ -0,0 +1,60 @@ +package process + +import ( + "github.com/crawlab-team/go-trace" + "os/exec" + "regexp" + "runtime" + "strings" +) + +var pidRegexp, _ = regexp.Compile("(?:^|\\s+)\\d+(?:$|\\s+)") + +func ProcessIdExists(id int) (ok bool) { + lines, err := ListProcess(string(rune(id))) + if err != nil { + return false + } + for _, line := range lines { + matched := pidRegexp.MatchString(line) + if matched { + return true + } + } + return false +} + +func ListProcess(text string) (lines []string, err error) { + if runtime.GOOS == "windows" { + return listProcessWindow(text) + } else { + return listProcessLinuxMac(text) + } +} + +func listProcessWindow(text string) (lines []string, err error) { + cmd := exec.Command("tasklist", "/fi", text) + out, err := cmd.CombinedOutput() + _, ok := err.(*exec.ExitError) + if !ok { + return nil, trace.TraceError(err) + } + lines = strings.Split(string(out), "\n") + return lines, nil +} + +func listProcessLinuxMac(text string) (lines []string, err error) { + cmd := exec.Command("ps", "aux") + out, err := cmd.CombinedOutput() + _, ok := err.(*exec.ExitError) + if !ok { + return nil, trace.TraceError(err) + } + _lines := strings.Split(string(out), "\n") + for _, l := range _lines { + if strings.Contains(l, text) { + lines = append(lines, l) + } + } + return lines, nil +} diff --git a/core/process/options.go b/core/process/options.go new file mode 100644 index 00000000..f0e046c9 --- /dev/null +++ b/core/process/options.go @@ -0,0 +1,20 @@ +package process + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "time" +) + +type DaemonOption func(d interfaces.ProcessDaemon) + +func WithDaemonMaxErrors(maxErrors int) DaemonOption { + return func(d interfaces.ProcessDaemon) { + d.SetMaxErrors(maxErrors) + } +} + +func WithExitTimeout(timeout time.Duration) DaemonOption { + return func(d interfaces.ProcessDaemon) { + + } +} diff --git a/core/result/options.go b/core/result/options.go new file mode 100644 index 00000000..59c63301 --- /dev/null +++ b/core/result/options.go @@ -0,0 +1,16 @@ +package result + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type Option func(opts *Options) + +type Options struct { + registryKey string // registry key + SpiderId primitive.ObjectID // data source id +} + +func WithRegistryKey(key string) Option { + return func(opts *Options) { + opts.registryKey = key + } +} diff --git a/core/result/service.go b/core/result/service.go new file mode 100644 index 00000000..3acd6321 --- /dev/null +++ b/core/result/service.go @@ -0,0 +1,89 @@ +package result + +import ( + "fmt" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson/primitive" + "sync" +) + +func NewResultService(registryKey string, s *models.Spider) (svc2 interfaces.ResultService, err error) { + // result service function + var fn interfaces.ResultServiceRegistryFn + + if registryKey == "" { + // default + fn = NewResultServiceMongo + } else { + // from registry + reg := GetResultServiceRegistry() + fn = reg.Get(registryKey) + if fn == nil { + return nil, errors.NewResultError(fmt.Sprintf("%s is not implemented", registryKey)) + } + } + + // generate result service + svc, err := fn(s.ColId, s.DataSourceId) + if err != nil { + return nil, trace.TraceError(err) + } + + return svc, nil +} + +var store = sync.Map{} + +func GetResultService(spiderId primitive.ObjectID, opts ...Option) (svc2 interfaces.ResultService, err error) { + // model service + modelSvc, err := service.GetService() + if err != nil { + return nil, trace.TraceError(err) + } + + // spider + s, err := modelSvc.GetSpiderById(spiderId) + if err != nil { + return nil, trace.TraceError(err) + } + + // apply options + _opts := &Options{} + for _, opt := range opts { + opt(_opts) + } + + // store key + storeKey := s.ColId.Hex() + ":" + s.DataSourceId.Hex() + + // attempt to load result service from store + res, _ := store.Load(storeKey) + if res != nil { + svc, ok := res.(interfaces.ResultService) + if ok { + return svc, nil + } + } + + // registry key + var registryKey string + ds, _ := modelSvc.GetDataSourceById(s.DataSourceId) + if ds != nil { + registryKey = ds.Type + } + + // create a new result service if not exists + svc, err := NewResultService(registryKey, s) + if err != nil { + return nil, err + } + + // save into store + store.Store(storeKey, svc) + + return svc, nil +} diff --git a/core/result/service_mongo.go b/core/result/service_mongo.go new file mode 100644 index 00000000..cdc6ff3f --- /dev/null +++ b/core/result/service_mongo.go @@ -0,0 +1,146 @@ +package result + +import ( + "time" + + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type ServiceMongo struct { + // dependencies + modelSvc service.ModelService + modelColSvc interfaces.ModelBaseService + + // internals + colId primitive.ObjectID // _id of models.DataCollection + dc *models.DataCollection // models.DataCollection + t time.Time +} + +func (svc *ServiceMongo) List(query generic.ListQuery, opts *generic.ListOptions) (results []interface{}, err error) { + _query := svc.getQuery(query) + _opts := svc.getOpts(opts) + return svc.getList(_query, _opts) +} + +func (svc *ServiceMongo) Count(query generic.ListQuery) (n int, err error) { + _query := svc.getQuery(query) + return svc.modelColSvc.Count(_query) +} + +func (svc *ServiceMongo) Insert(docs ...interface{}) (err error) { + if svc.dc.Dedup.Enabled && len(svc.dc.Dedup.Keys) > 0 { + for _, doc := range docs { + hash, err := utils.GetResultHash(doc, svc.dc.Dedup.Keys) + if err != nil { + return err + } + doc.(interfaces.Result).SetValue(constants.HashKey, hash) + query := bson.M{constants.HashKey: hash} + switch svc.dc.Dedup.Type { + case constants.DedupTypeOverwrite: + err = mongo.GetMongoCol(svc.dc.Name).ReplaceWithOptions(query, doc, &options.ReplaceOptions{Upsert: &[]bool{true}[0]}) + if err != nil { + return trace.TraceError(err) + } + default: + var o bson.M + err := mongo.GetMongoCol(svc.dc.Name).Find(query, &mongo.FindOptions{Limit: 1}).One(&o) + if err == nil { + // exists, ignore + continue + } + if err != mongo2.ErrNoDocuments { + // error + return trace.TraceError(err) + } + // not exists, insert + _, err = mongo.GetMongoCol(svc.dc.Name).Insert(doc) + if err != nil { + return trace.TraceError(err) + } + } + } + } else { + _, err = mongo.GetMongoCol(svc.dc.Name).InsertMany(docs) + if err != nil { + return trace.TraceError(err) + } + } + return nil +} + +func (svc *ServiceMongo) Index(fields []string) { + for _, field := range fields { + _ = mongo.GetMongoCol(svc.dc.Name).CreateIndex(mongo2.IndexModel{Keys: bson.M{field: 1}}) + } +} + +func (svc *ServiceMongo) SetTime(t time.Time) { + svc.t = t +} + +func (svc *ServiceMongo) GetTime() (t time.Time) { + return svc.t +} + +func (svc *ServiceMongo) getList(query bson.M, opts *mongo.FindOptions) (results []interface{}, err error) { + list, err := svc.modelColSvc.GetList(query, opts) + if err != nil { + return nil, err + } + for _, d := range list.GetModels() { + r, ok := d.(interfaces.Result) + if ok { + results = append(results, r) + } + } + return results, nil +} + +func (svc *ServiceMongo) getQuery(query generic.ListQuery) (res bson.M) { + return utils.GetMongoQuery(query) +} + +func (svc *ServiceMongo) getOpts(opts *generic.ListOptions) (res *mongo.FindOptions) { + return utils.GetMongoOpts(opts) +} + +func NewResultServiceMongo(colId primitive.ObjectID, _ primitive.ObjectID) (svc2 interfaces.ResultService, err error) { + // service + svc := &ServiceMongo{ + colId: colId, + t: time.Now(), + } + + // dependency injection + svc.modelSvc, err = service.GetService() + if err != nil { + return nil, err + } + + // data collection + svc.dc, _ = svc.modelSvc.GetDataCollectionById(colId) + go func() { + for { + time.Sleep(1 * time.Second) + svc.dc, _ = svc.modelSvc.GetDataCollectionById(colId) + } + }() + + // data collection model service + svc.modelColSvc = service.GetBaseServiceByColName(interfaces.ModelIdResult, svc.dc.Name) + + return svc, nil +} diff --git a/core/result/service_registry.go b/core/result/service_registry.go new file mode 100644 index 00000000..2c635397 --- /dev/null +++ b/core/result/service_registry.go @@ -0,0 +1,48 @@ +package result + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "sync" +) + +type ServiceRegistry struct { + // internals + services sync.Map +} + +func (r *ServiceRegistry) Register(key string, fn interfaces.ResultServiceRegistryFn) { + r.services.Store(key, fn) +} + +func (r *ServiceRegistry) Unregister(key string) { + r.services.Delete(key) +} + +func (r *ServiceRegistry) Get(key string) (fn interfaces.ResultServiceRegistryFn) { + res, ok := r.services.Load(key) + if ok { + fn, ok = res.(interfaces.ResultServiceRegistryFn) + if !ok { + return nil + } + return fn + } + return nil +} + +func NewResultServiceRegistry() (r interfaces.ResultServiceRegistry) { + r = &ServiceRegistry{ + services: sync.Map{}, + } + return r +} + +var _svc interfaces.ResultServiceRegistry + +func GetResultServiceRegistry() (r interfaces.ResultServiceRegistry) { + if _svc != nil { + return _svc + } + _svc = NewResultServiceRegistry() + return _svc +} diff --git a/core/result/test/base.go b/core/result/test/base.go new file mode 100644 index 00000000..14331b20 --- /dev/null +++ b/core/result/test/base.go @@ -0,0 +1,76 @@ +package test + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/result" + "go.uber.org/dig" + "testing" +) + +func init() { + T = NewTest() +} + +var T *Test + +type Test struct { + // dependencies + modelSvc service.ModelService + resultSvc interfaces.ResultService + + // test data + TestColName string + TestCol *mongo.Col + TestDc *models.DataCollection +} + +func (t *Test) Setup(t2 *testing.T) { + t2.Cleanup(t.Cleanup) +} + +func (t *Test) Cleanup() { + _ = t.modelSvc.DropAll() +} + +func NewTest() *Test { + var err error + + // test + t := &Test{ + TestColName: "test_results", + } + + // dependency injection + c := dig.New() + if err := c.Provide(service.NewService); err != nil { + panic(err) + } + if err := c.Invoke(func( + modelSvc service.ModelService, + ) { + t.modelSvc = modelSvc + }); err != nil { + panic(err) + } + + // data collection + t.TestDc = &models.DataCollection{ + Name: t.TestColName, + } + if err := delegate.NewModelDelegate(t.TestDc).Add(); err != nil { + panic(err) + } + t.TestCol = mongo.GetMongoCol(t.TestColName) + + // result service + t.resultSvc, err = result.GetResultService(t.TestDc.GetId()) + if err != nil { + panic(err) + } + + return t +} diff --git a/core/result/test/service_test.go b/core/result/test/service_test.go new file mode 100644 index 00000000..b44faf4e --- /dev/null +++ b/core/result/test/service_test.go @@ -0,0 +1,67 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/stretchr/testify/require" + "testing" +) + +func TestResultService_GetList(t *testing.T) { + var err error + T.Setup(t) + + n := 1000 + var docs []interface{} + for i := 0; i < n; i++ { + d := &models.Result{ + "i": i, + } + docs = append(docs, d) + } + _, err = T.TestCol.InsertMany(docs) + require.Nil(t, err) + + // get all + results, err := T.resultSvc.List(nil, nil) + require.Nil(t, err) + require.Equal(t, n, len(results)) + + //query := bson.M{ + // "i": bson.M{ + // "$lt": n / 2, + // }, + //} + //results, err = T.resultSvc.List(query, nil) + //require.Nil(t, err) + //require.Equal(t, n/2, len(results)) +} + +func TestResultService_Count(t *testing.T) { + var err error + T.Setup(t) + + n := 1000 + var docs []interface{} + for i := 0; i < n; i++ { + d := &models.Result{ + "i": i, + } + docs = append(docs, d) + } + _, err = T.TestCol.InsertMany(docs) + require.Nil(t, err) + + // get all + total, err := T.resultSvc.Count(nil) + require.Nil(t, err) + require.Equal(t, n, total) + + //query := bson.M{ + // "i": bson.M{ + // "$lt": n / 2, + // }, + //} + //total, err = T.resultSvc.Count(query) + //require.Nil(t, err) + //require.Equal(t, n/2, total) +} diff --git a/core/routes/group.go b/core/routes/group.go new file mode 100644 index 00000000..cade1af4 --- /dev/null +++ b/core/routes/group.go @@ -0,0 +1,20 @@ +package routes + +import ( + "github.com/crawlab-team/crawlab/core/middlewares" + "github.com/gin-gonic/gin" +) + +type RouterGroups struct { + AuthGroup *gin.RouterGroup + AnonymousGroup *gin.RouterGroup + FilerGroup *gin.RouterGroup +} + +func NewRouterGroups(app *gin.Engine) (groups *RouterGroups) { + return &RouterGroups{ + AuthGroup: app.Group("/", middlewares.AuthorizationMiddleware()), + AnonymousGroup: app.Group("/"), + FilerGroup: app.Group("/filer", middlewares.FilerAuthorizationMiddleware()), + } +} diff --git a/core/routes/router.go b/core/routes/router.go new file mode 100644 index 00000000..c82603bc --- /dev/null +++ b/core/routes/router.go @@ -0,0 +1,178 @@ +package routes + +import ( + "fmt" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/controllers" + "github.com/gin-gonic/gin" + "net/http" + "path" +) + +type RouterServiceInterface interface { + RegisterControllerToGroup(group *gin.RouterGroup, basePath string, ctr controllers.ListController) + RegisterHandlerToGroup(group *gin.RouterGroup, path string, method string, handler gin.HandlerFunc) +} + +type RouterService struct { + app *gin.Engine +} + +func NewRouterService(app *gin.Engine) (svc *RouterService) { + return &RouterService{ + app: app, + } +} + +func (svc *RouterService) RegisterControllerToGroup(group *gin.RouterGroup, basePath string, ctr controllers.BasicController) { + group.GET(basePath, ctr.Get) + group.POST(basePath, ctr.Post) + group.PUT(basePath, ctr.Put) + group.DELETE(basePath, ctr.Delete) +} + +func (svc *RouterService) RegisterListControllerToGroup(group *gin.RouterGroup, basePath string, ctr controllers.ListController) { + group.GET(basePath+"/:id", ctr.Get) + group.GET(basePath, ctr.GetList) + group.POST(basePath, ctr.Post) + group.POST(basePath+"/batch", ctr.PostList) + group.PUT(basePath+"/:id", ctr.Put) + group.PUT(basePath, ctr.PutList) + group.DELETE(basePath+"/:id", ctr.Delete) + group.DELETE(basePath, ctr.DeleteList) +} + +func (svc *RouterService) RegisterActionControllerToGroup(group *gin.RouterGroup, basePath string, ctr controllers.ActionController) { + for _, action := range ctr.Actions() { + routerPath := path.Join(basePath, action.Path) + switch action.Method { + case http.MethodGet: + group.GET(routerPath, action.HandlerFunc) + case http.MethodPost: + group.POST(routerPath, action.HandlerFunc) + case http.MethodPut: + group.PUT(routerPath, action.HandlerFunc) + case http.MethodDelete: + group.DELETE(routerPath, action.HandlerFunc) + } + } +} + +func (svc *RouterService) RegisterListActionControllerToGroup(group *gin.RouterGroup, basePath string, ctr controllers.ListActionController) { + svc.RegisterListControllerToGroup(group, basePath, ctr) + svc.RegisterActionControllerToGroup(group, basePath, ctr) +} + +func (svc *RouterService) RegisterHandlerToGroup(group *gin.RouterGroup, path string, method string, handler gin.HandlerFunc) { + switch method { + case http.MethodGet: + group.GET(path, handler) + case http.MethodPost: + group.POST(path, handler) + case http.MethodPut: + group.PUT(path, handler) + case http.MethodDelete: + group.DELETE(path, handler) + default: + log.Warn(fmt.Sprintf("%s is not a valid http method", method)) + } +} + +func InitRoutes(app *gin.Engine) (err error) { + // routes groups + groups := NewRouterGroups(app) + + // router service + svc := NewRouterService(app) + + // register routes + registerRoutesAnonymousGroup(svc, groups) + registerRoutesAuthGroup(svc, groups) + registerRoutesFilterGroup(svc, groups) + + return nil +} + +func registerRoutesAnonymousGroup(svc *RouterService, groups *RouterGroups) { + // login + svc.RegisterActionControllerToGroup(groups.AnonymousGroup, "/", controllers.LoginController) + + // version + svc.RegisterActionControllerToGroup(groups.AnonymousGroup, "/version", controllers.VersionController) + + // system info + svc.RegisterActionControllerToGroup(groups.AnonymousGroup, "/system-info", controllers.SystemInfoController) + + // demo + svc.RegisterActionControllerToGroup(groups.AnonymousGroup, "/demo", controllers.DemoController) + + // sync + svc.RegisterActionControllerToGroup(groups.AnonymousGroup, "/sync", controllers.SyncController) +} + +func registerRoutesAuthGroup(svc *RouterService, groups *RouterGroups) { + // node + svc.RegisterListControllerToGroup(groups.AuthGroup, "/nodes", controllers.NodeController) + + // project + svc.RegisterListControllerToGroup(groups.AuthGroup, "/projects", controllers.ProjectController) + + // user + svc.RegisterListActionControllerToGroup(groups.AuthGroup, "/users", controllers.UserController) + + // spider + svc.RegisterListActionControllerToGroup(groups.AuthGroup, "/spiders", controllers.SpiderController) + + // task + svc.RegisterListActionControllerToGroup(groups.AuthGroup, "/tasks", controllers.TaskController) + + // tag + svc.RegisterListControllerToGroup(groups.AuthGroup, "/tags", controllers.TagController) + + // setting + svc.RegisterListControllerToGroup(groups.AuthGroup, "/settings", controllers.SettingController) + + // data collection + svc.RegisterListControllerToGroup(groups.AuthGroup, "/data/collections", controllers.DataCollectionController) + + // result + svc.RegisterActionControllerToGroup(groups.AuthGroup, "/results", controllers.ResultController) + + // schedule + svc.RegisterListActionControllerToGroup(groups.AuthGroup, "/schedules", controllers.ScheduleController) + + // stats + svc.RegisterActionControllerToGroup(groups.AuthGroup, "/stats", controllers.StatsController) + + // token + svc.RegisterListControllerToGroup(groups.AuthGroup, "/tokens", controllers.TokenController) + + // git + svc.RegisterListControllerToGroup(groups.AuthGroup, "/gits", controllers.GitController) + + // role + svc.RegisterListControllerToGroup(groups.AuthGroup, "/roles", controllers.RoleController) + + // permission + svc.RegisterListControllerToGroup(groups.AuthGroup, "/permissions", controllers.PermissionController) + + // export + svc.RegisterActionControllerToGroup(groups.AuthGroup, "/export", controllers.ExportController) + + // notification + svc.RegisterActionControllerToGroup(groups.AuthGroup, "/notifications", controllers.NotificationController) + + // filter + svc.RegisterActionControllerToGroup(groups.AuthGroup, "/filters", controllers.FilterController) + + // data sources + svc.RegisterListActionControllerToGroup(groups.AuthGroup, "/data-sources", controllers.DataSourceController) + + // environments + svc.RegisterListActionControllerToGroup(groups.AuthGroup, "/environments", controllers.EnvironmentController) +} + +func registerRoutesFilterGroup(svc *RouterService, groups *RouterGroups) { + // filer + svc.RegisterActionControllerToGroup(groups.FilerGroup, "", controllers.FilerController) +} diff --git a/core/routes/router_test.go b/core/routes/router_test.go new file mode 100644 index 00000000..7b72c852 --- /dev/null +++ b/core/routes/router_test.go @@ -0,0 +1,26 @@ +package routes + +import ( + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" + "net/http" + "testing" + "time" +) + +func TestInitRoutes(t *testing.T) { + app := gin.New() + err := InitRoutes(app) + require.Nil(t, err) + + srv := &http.Server{ + Handler: app, + Addr: "localhost:8000", + } + go func() { + err = srv.ListenAndServe() + require.Nil(t, err) + }() + + time.Sleep(5 * time.Second) +} diff --git a/core/schedule/logger.go b/core/schedule/logger.go new file mode 100644 index 00000000..fa6bf834 --- /dev/null +++ b/core/schedule/logger.go @@ -0,0 +1,35 @@ +package schedule + +import ( + "fmt" + "github.com/apex/log" + "github.com/crawlab-team/go-trace" + "github.com/robfig/cron/v3" + "strings" +) + +type Logger struct { +} + +func (l *Logger) Info(msg string, keysAndValues ...interface{}) { + p := l.getPlaceholder(len(keysAndValues)) + log.Infof(fmt.Sprintf("cron: %s %s", msg, p), keysAndValues...) +} + +func (l *Logger) Error(err error, msg string, keysAndValues ...interface{}) { + p := l.getPlaceholder(len(keysAndValues)) + log.Errorf(fmt.Sprintf("cron: %s %s", msg, p), keysAndValues...) + trace.PrintError(err) +} + +func (l *Logger) getPlaceholder(n int) (s string) { + var arr []string + for i := 0; i < n; i++ { + arr = append(arr, "%v") + } + return strings.Join(arr, " ") +} + +func NewLogger() cron.Logger { + return &Logger{} +} diff --git a/core/schedule/options.go b/core/schedule/options.go new file mode 100644 index 00000000..6ad22f99 --- /dev/null +++ b/core/schedule/options.go @@ -0,0 +1,37 @@ +package schedule + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "time" +) + +type Option func(svc interfaces.ScheduleService) + +func WithConfigPath(path string) Option { + return func(svc interfaces.ScheduleService) { + svc.SetConfigPath(path) + } +} + +func WithLocation(loc *time.Location) Option { + return func(svc interfaces.ScheduleService) { + svc.SetLocation(loc) + } +} + +func WithDelayIfStillRunning() Option { + return func(svc interfaces.ScheduleService) { + svc.SetDelay(true) + } +} + +func WithSkipIfStillRunning() Option { + return func(svc interfaces.ScheduleService) { + svc.SetSkip(true) + } +} + +func WithUpdateInterval(interval time.Duration) Option { + return func(svc interfaces.ScheduleService) { + } +} diff --git a/core/schedule/service.go b/core/schedule/service.go new file mode 100644 index 00000000..f4b978da --- /dev/null +++ b/core/schedule/service.go @@ -0,0 +1,287 @@ +package schedule + +import ( + "github.com/crawlab-team/crawlab/core/config" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/robfig/cron/v3" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "sync" + "time" +) + +type Service struct { + // dependencies + interfaces.WithConfigPath + modelSvc service.ModelService + adminSvc interfaces.SpiderAdminService + + // settings variables + loc *time.Location + delay bool + skip bool + updateInterval time.Duration + + // internals + cron *cron.Cron + logger cron.Logger + schedules []models.Schedule + stopped bool + mu sync.Mutex +} + +func (svc *Service) GetLocation() (loc *time.Location) { + return svc.loc +} + +func (svc *Service) SetLocation(loc *time.Location) { + svc.loc = loc +} + +func (svc *Service) GetDelay() (delay bool) { + return svc.delay +} + +func (svc *Service) SetDelay(delay bool) { + svc.delay = delay +} + +func (svc *Service) GetSkip() (skip bool) { + return svc.skip +} + +func (svc *Service) SetSkip(skip bool) { + svc.skip = skip +} + +func (svc *Service) GetUpdateInterval() (interval time.Duration) { + return svc.updateInterval +} + +func (svc *Service) SetUpdateInterval(interval time.Duration) { + svc.updateInterval = interval +} + +func (svc *Service) Init() (err error) { + return svc.fetch() +} + +func (svc *Service) Start() { + svc.cron.Start() + go svc.Update() +} + +func (svc *Service) Wait() { + utils.DefaultWait() + svc.Stop() +} + +func (svc *Service) Stop() { + svc.stopped = true + svc.cron.Stop() +} + +func (svc *Service) Enable(s interfaces.Schedule, args ...interface{}) (err error) { + svc.mu.Lock() + defer svc.mu.Unlock() + + id, err := svc.cron.AddFunc(s.GetCron(), svc.schedule(s.GetId())) + if err != nil { + return trace.TraceError(err) + } + s.SetEnabled(true) + s.SetEntryId(id) + u := utils.GetUserFromArgs(args...) + return delegate.NewModelDelegate(s, u).Save() +} + +func (svc *Service) Disable(s interfaces.Schedule, args ...interface{}) (err error) { + svc.mu.Lock() + defer svc.mu.Unlock() + + svc.cron.Remove(s.GetEntryId()) + s.SetEnabled(false) + s.SetEntryId(-1) + u := utils.GetUserFromArgs(args...) + return delegate.NewModelDelegate(s, u).Save() +} + +func (svc *Service) Update() { + for { + if svc.stopped { + return + } + + svc.update() + + time.Sleep(svc.updateInterval) + } +} + +func (svc *Service) GetCron() (c *cron.Cron) { + return svc.cron +} + +func (svc *Service) update() { + // fetch enabled schedules + if err := svc.fetch(); err != nil { + trace.PrintError(err) + return + } + + // entry id map + entryIdsMap := svc.getEntryIdsMap() + + // iterate enabled schedules + for _, s := range svc.schedules { + _, ok := entryIdsMap[s.EntryId] + if ok { + entryIdsMap[s.EntryId] = true + } else { + if err := svc.Enable(&s); err != nil { + trace.PrintError(err) + continue + } + } + } + + // remove non-existent entries + for id, ok := range entryIdsMap { + if !ok { + svc.cron.Remove(id) + } + } +} + +func (svc *Service) getEntryIdsMap() (res map[cron.EntryID]bool) { + res = map[cron.EntryID]bool{} + for _, e := range svc.cron.Entries() { + res[e.ID] = false + } + return res +} + +func (svc *Service) fetch() (err error) { + query := bson.M{ + "enabled": true, + } + svc.schedules, err = svc.modelSvc.GetScheduleList(query, nil) + if err != nil { + return err + } + return nil +} + +func (svc *Service) schedule(id primitive.ObjectID) (fn func()) { + return func() { + // schedule + s, err := svc.modelSvc.GetScheduleById(id) + if err != nil { + trace.PrintError(err) + return + } + + // spider + spider, err := svc.modelSvc.GetSpiderById(s.GetSpiderId()) + if err != nil { + trace.PrintError(err) + return + } + + // options + opts := &interfaces.SpiderRunOptions{ + Mode: s.GetMode(), + NodeIds: s.GetNodeIds(), + Cmd: s.GetCmd(), + Param: s.GetParam(), + Priority: s.GetPriority(), + ScheduleId: s.GetId(), + UserId: s.UserId, + } + + // normalize options + if opts.Mode == "" { + opts.Mode = spider.Mode + } + if len(opts.NodeIds) == 0 { + opts.NodeIds = spider.NodeIds + } + if opts.Cmd == "" { + opts.Cmd = spider.Cmd + } + if opts.Param == "" { + opts.Param = spider.Param + } + if opts.Priority == 0 { + if spider.Priority > 0 { + opts.Priority = spider.Priority + } else { + opts.Priority = 5 + } + } + + // schedule or assign a task in the task queue + if _, err := svc.adminSvc.Schedule(s.GetSpiderId(), opts); err != nil { + trace.PrintError(err) + } + } +} + +func NewScheduleService() (svc2 interfaces.ScheduleService, err error) { + // service + svc := &Service{ + WithConfigPath: config.NewConfigPathService(), + loc: time.Local, + // TODO: implement delay and skip + delay: false, + skip: false, + updateInterval: 1 * time.Minute, + } + + // dependency injection + if err := container.GetContainer().Invoke(func( + modelSvc service.ModelService, + adminSvc interfaces.SpiderAdminService, + ) { + svc.modelSvc = modelSvc + svc.adminSvc = adminSvc + }); err != nil { + return nil, trace.TraceError(err) + } + + // logger + svc.logger = NewLogger() + + // cron + svc.cron = cron.New( + cron.WithLogger(svc.logger), + cron.WithLocation(svc.loc), + cron.WithChain(cron.Recover(svc.logger)), + ) + + // initialize + if err := svc.Init(); err != nil { + return nil, err + } + + return svc, nil +} + +var svc interfaces.ScheduleService + +func GetScheduleService() (res interfaces.ScheduleService, err error) { + if svc != nil { + return svc, nil + } + svc, err = NewScheduleService() + if err != nil { + return nil, err + } + return svc, nil +} diff --git a/core/schedule/service_v2.go b/core/schedule/service_v2.go new file mode 100644 index 00000000..c1cffbef --- /dev/null +++ b/core/schedule/service_v2.go @@ -0,0 +1,283 @@ +package schedule + +import ( + "github.com/crawlab-team/crawlab/core/config" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/spider/admin" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/robfig/cron/v3" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "sync" + "time" +) + +type ServiceV2 struct { + // dependencies + interfaces.WithConfigPath + modelSvc *service.ModelServiceV2[models.ScheduleV2] + adminSvc *admin.ServiceV2 + + // settings variables + loc *time.Location + delay bool + skip bool + updateInterval time.Duration + + // internals + cron *cron.Cron + logger cron.Logger + schedules []models.ScheduleV2 + stopped bool + mu sync.Mutex +} + +func (svc *ServiceV2) GetLocation() (loc *time.Location) { + return svc.loc +} + +func (svc *ServiceV2) SetLocation(loc *time.Location) { + svc.loc = loc +} + +func (svc *ServiceV2) GetDelay() (delay bool) { + return svc.delay +} + +func (svc *ServiceV2) SetDelay(delay bool) { + svc.delay = delay +} + +func (svc *ServiceV2) GetSkip() (skip bool) { + return svc.skip +} + +func (svc *ServiceV2) SetSkip(skip bool) { + svc.skip = skip +} + +func (svc *ServiceV2) GetUpdateInterval() (interval time.Duration) { + return svc.updateInterval +} + +func (svc *ServiceV2) SetUpdateInterval(interval time.Duration) { + svc.updateInterval = interval +} + +func (svc *ServiceV2) Init() (err error) { + return svc.fetch() +} + +func (svc *ServiceV2) Start() { + svc.cron.Start() + go svc.Update() +} + +func (svc *ServiceV2) Wait() { + utils.DefaultWait() + svc.Stop() +} + +func (svc *ServiceV2) Stop() { + svc.stopped = true + svc.cron.Stop() +} + +func (svc *ServiceV2) Enable(s models.ScheduleV2, by primitive.ObjectID) (err error) { + svc.mu.Lock() + defer svc.mu.Unlock() + + id, err := svc.cron.AddFunc(s.Cron, svc.schedule(s.Id)) + if err != nil { + return trace.TraceError(err) + } + s.Enabled = true + s.EntryId = id + s.SetUpdated(by) + return svc.modelSvc.ReplaceById(s.Id, s) +} + +func (svc *ServiceV2) Disable(s models.ScheduleV2, by primitive.ObjectID) (err error) { + svc.mu.Lock() + defer svc.mu.Unlock() + + svc.cron.Remove(s.EntryId) + s.Enabled = false + s.EntryId = -1 + s.SetUpdated(by) + return svc.modelSvc.ReplaceById(s.Id, s) +} + +func (svc *ServiceV2) Update() { + for { + if svc.stopped { + return + } + + svc.update() + + time.Sleep(svc.updateInterval) + } +} + +func (svc *ServiceV2) GetCron() (c *cron.Cron) { + return svc.cron +} + +func (svc *ServiceV2) update() { + // fetch enabled schedules + if err := svc.fetch(); err != nil { + trace.PrintError(err) + return + } + + // entry id map + entryIdsMap := svc.getEntryIdsMap() + + // iterate enabled schedules + for _, s := range svc.schedules { + _, ok := entryIdsMap[s.EntryId] + if ok { + entryIdsMap[s.EntryId] = true + } else { + if !s.Enabled { + err := svc.Enable(s, s.GetCreatedBy()) + if err != nil { + trace.PrintError(err) + continue + } + } + } + } + + // remove non-existent entries + for id, ok := range entryIdsMap { + if !ok { + svc.cron.Remove(id) + } + } +} + +func (svc *ServiceV2) getEntryIdsMap() (res map[cron.EntryID]bool) { + res = map[cron.EntryID]bool{} + for _, e := range svc.cron.Entries() { + res[e.ID] = false + } + return res +} + +func (svc *ServiceV2) fetch() (err error) { + query := bson.M{ + "enabled": true, + } + svc.schedules, err = svc.modelSvc.GetMany(query, nil) + if err != nil { + return err + } + return nil +} + +func (svc *ServiceV2) schedule(id primitive.ObjectID) (fn func()) { + return func() { + // schedule + s, err := svc.modelSvc.GetById(id) + if err != nil { + trace.PrintError(err) + return + } + + // spider + spider, err := service.NewModelServiceV2[models.SpiderV2]().GetById(s.SpiderId) + if err != nil { + trace.PrintError(err) + return + } + + // options + opts := &interfaces.SpiderRunOptions{ + Mode: s.Mode, + NodeIds: s.NodeIds, + Cmd: s.Cmd, + Param: s.Param, + Priority: s.Priority, + ScheduleId: s.Id, + UserId: s.GetCreatedBy(), + } + + // normalize options + if opts.Mode == "" { + opts.Mode = spider.Mode + } + if len(opts.NodeIds) == 0 { + opts.NodeIds = spider.NodeIds + } + if opts.Cmd == "" { + opts.Cmd = spider.Cmd + } + if opts.Param == "" { + opts.Param = spider.Param + } + if opts.Priority == 0 { + if spider.Priority > 0 { + opts.Priority = spider.Priority + } else { + opts.Priority = 5 + } + } + + // schedule or assign a task in the task queue + if _, err := svc.adminSvc.Schedule(s.SpiderId, opts); err != nil { + trace.PrintError(err) + } + } +} + +func NewScheduleServiceV2() (svc2 *ServiceV2, err error) { + // service + svc := &ServiceV2{ + WithConfigPath: config.NewConfigPathService(), + loc: time.Local, + // TODO: implement delay and skip + delay: false, + skip: false, + updateInterval: 1 * time.Minute, + } + svc.adminSvc, err = admin.GetSpiderAdminServiceV2() + if err != nil { + return nil, err + } + svc.modelSvc = service.NewModelServiceV2[models.ScheduleV2]() + + // logger + svc.logger = NewLogger() + + // cron + svc.cron = cron.New( + cron.WithLogger(svc.logger), + cron.WithLocation(svc.loc), + cron.WithChain(cron.Recover(svc.logger)), + ) + + // initialize + if err := svc.Init(); err != nil { + return nil, err + } + + return svc, nil +} + +var svcV2 *ServiceV2 + +func GetScheduleServiceV2() (res *ServiceV2, err error) { + if svcV2 != nil { + return svcV2, nil + } + svcV2, err = NewScheduleServiceV2() + if err != nil { + return nil, err + } + return svcV2, nil +} diff --git a/core/schedule/test/base.go b/core/schedule/test/base.go new file mode 100644 index 00000000..cd7c885e --- /dev/null +++ b/core/schedule/test/base.go @@ -0,0 +1,91 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/schedule" + "go.uber.org/dig" + "testing" +) + +func init() { + var err error + T, err = NewTest() + if err != nil { + panic(err) + } +} + +var T *Test + +type Test struct { + // dependencies + modelSvc service.ModelService + scheduleSvc interfaces.ScheduleService + + // test data + TestSchedule interfaces.Schedule + TestSpider interfaces.Spider + ScriptName string + Script string +} + +func (t *Test) Setup(t2 *testing.T) { + t.scheduleSvc.Start() + t2.Cleanup(t.Cleanup) +} + +func (t *Test) Cleanup() { + t.scheduleSvc.Stop() + _ = t.modelSvc.GetBaseService(interfaces.ModelIdTask).Delete(nil) +} + +func NewTest() (t *Test, err error) { + // test + t = &Test{ + TestSpider: &models.Spider{ + Name: "test_spider", + Cmd: "go run main.go", + }, + ScriptName: "main.go", + Script: `package main +import "fmt" +func main() { + fmt.Println("it works") +}`, + } + + // dependency injection + c := dig.New() + if err := c.Provide(service.GetService); err != nil { + return nil, err + } + if err := c.Provide(schedule.NewScheduleService); err != nil { + return nil, err + } + if err := c.Invoke(func(modelSvc service.ModelService, scheduleSvc interfaces.ScheduleService) { + t.modelSvc = modelSvc + t.scheduleSvc = scheduleSvc + }); err != nil { + return nil, err + } + + // add spider to db + if err := delegate.NewModelDelegate(t.TestSpider).Add(); err != nil { + return nil, err + } + + // test schedule + t.TestSchedule = &models.Schedule{ + Name: "test_schedule", + SpiderId: t.TestSpider.GetId(), + Cron: "* * * * *", + } + if err := delegate.NewModelDelegate(t.TestSchedule).Add(); err != nil { + return nil, err + } + + return t, nil +} diff --git a/core/schedule/test/schedule_service_test.go b/core/schedule/test/schedule_service_test.go new file mode 100644 index 00000000..7a815da1 --- /dev/null +++ b/core/schedule/test/schedule_service_test.go @@ -0,0 +1,44 @@ +package test + +import ( + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestScheduleService_Enable_Disable(t *testing.T) { + var err error + T.Setup(t) + + time.Sleep(1 * time.Second) + err = T.scheduleSvc.Enable(T.TestSchedule) + require.Nil(t, err) + time.Sleep(1 * time.Second) + + require.True(t, T.TestSchedule.GetEnabled()) + require.Greater(t, int(T.TestSchedule.GetEntryId()), -1) + e := T.scheduleSvc.GetCron().Entry(T.TestSchedule.GetEntryId()) + require.Equal(t, T.TestSchedule.GetEntryId(), e.ID) + time.Sleep(1 * time.Second) + + err = T.scheduleSvc.Disable(T.TestSchedule) + require.False(t, T.TestSchedule.GetEnabled()) + require.Equal(t, 0, len(T.scheduleSvc.GetCron().Entries())) +} + +func TestScheduleService_Run(t *testing.T) { + var err error + T.Setup(t) + + time.Sleep(1 * time.Second) + err = T.scheduleSvc.Enable(T.TestSchedule) + require.Nil(t, err) + time.Sleep(1 * time.Minute) + + tasks, err := T.modelSvc.GetTaskList(nil, nil) + require.Nil(t, err) + require.Greater(t, len(tasks), 0) + for _, task := range tasks { + require.False(t, task.ScheduleId.IsZero()) + } +} diff --git a/core/spider/admin/options.go b/core/spider/admin/options.go new file mode 100644 index 00000000..32e08e76 --- /dev/null +++ b/core/spider/admin/options.go @@ -0,0 +1,11 @@ +package admin + +import "github.com/crawlab-team/crawlab/core/interfaces" + +type Option func(svc interfaces.SpiderAdminService) + +func WithConfigPath(path string) Option { + return func(svc interfaces.SpiderAdminService) { + svc.SetConfigPath(path) + } +} diff --git a/core/spider/admin/service.go b/core/spider/admin/service.go new file mode 100644 index 00000000..83d6ac9e --- /dev/null +++ b/core/spider/admin/service.go @@ -0,0 +1,352 @@ +package admin + +import ( + "context" + "github.com/apex/log" + vcs "github.com/crawlab-team/crawlab-vcs" + config2 "github.com/crawlab-team/crawlab/core/config" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/google/uuid" + "github.com/robfig/cron/v3" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "os" + "path" + "path/filepath" + "sync" + "time" +) + +type Service struct { + // dependencies + nodeCfgSvc interfaces.NodeConfigService + modelSvc service.ModelService + schedulerSvc interfaces.TaskSchedulerService + cron *cron.Cron + syncLock bool + + // settings + cfgPath string +} + +func (svc *Service) GetConfigPath() (path string) { + return svc.cfgPath +} + +func (svc *Service) SetConfigPath(path string) { + svc.cfgPath = path +} + +func (svc *Service) Start() (err error) { + return svc.SyncGit() +} + +func (svc *Service) Schedule(id primitive.ObjectID, opts *interfaces.SpiderRunOptions) (taskIds []primitive.ObjectID, err error) { + // spider + s, err := svc.modelSvc.GetSpiderById(id) + if err != nil { + return nil, err + } + + // assign tasks + return svc.scheduleTasks(s, opts) +} + +func (svc *Service) Clone(id primitive.ObjectID, opts *interfaces.SpiderCloneOptions) (err error) { + // TODO: implement + return nil +} + +func (svc *Service) Delete(id primitive.ObjectID) (err error) { + panic("implement me") +} + +func (svc *Service) SyncGit() (err error) { + if _, err = svc.cron.AddFunc("* * * * *", svc.syncGit); err != nil { + return trace.TraceError(err) + } + svc.cron.Start() + return nil +} + +func (svc *Service) SyncGitOne(g interfaces.Git) (err error) { + svc.syncGitOne(g) + return nil +} + +func (svc *Service) Export(id primitive.ObjectID) (filePath string, err error) { + // spider fs + workspacePath := viper.GetString("workspace") + spiderFolderPath := filepath.Join(workspacePath, id.Hex()) + + // zip files in workspace + dirPath := spiderFolderPath + zipFilePath := path.Join(os.TempDir(), uuid.New().String()+".zip") + if err := utils.ZipDirectory(dirPath, zipFilePath); err != nil { + return "", trace.TraceError(err) + } + + return zipFilePath, nil +} + +func (svc *Service) scheduleTasks(s *models.Spider, opts *interfaces.SpiderRunOptions) (taskIds []primitive.ObjectID, err error) { + // main task + mainTask := &models.Task{ + SpiderId: s.Id, + Mode: opts.Mode, + NodeIds: opts.NodeIds, + Cmd: opts.Cmd, + Param: opts.Param, + ScheduleId: opts.ScheduleId, + Priority: opts.Priority, + UserId: opts.UserId, + CreateTs: time.Now(), + } + + // normalize + if mainTask.Mode == "" { + mainTask.Mode = s.Mode + } + if mainTask.NodeIds == nil { + mainTask.NodeIds = s.NodeIds + } + if mainTask.Cmd == "" { + mainTask.Cmd = s.Cmd + } + if mainTask.Param == "" { + mainTask.Param = s.Param + } + if mainTask.Priority == 0 { + mainTask.Priority = s.Priority + } + + if svc.isMultiTask(opts) { + // multi tasks + nodeIds, err := svc.getNodeIds(opts) + if err != nil { + return nil, err + } + for _, nodeId := range nodeIds { + t := &models.Task{ + SpiderId: s.Id, + Mode: opts.Mode, + Cmd: opts.Cmd, + Param: opts.Param, + NodeId: nodeId, + ScheduleId: opts.ScheduleId, + Priority: opts.Priority, + UserId: opts.UserId, + CreateTs: time.Now(), + } + t2, err := svc.schedulerSvc.Enqueue(t) + if err != nil { + return nil, err + } + taskIds = append(taskIds, t2.GetId()) + } + } else { + // single task + nodeIds, err := svc.getNodeIds(opts) + if err != nil { + return nil, err + } + if len(nodeIds) > 0 { + mainTask.NodeId = nodeIds[0] + } + t2, err := svc.schedulerSvc.Enqueue(mainTask) + if err != nil { + return nil, err + } + taskIds = append(taskIds, t2.GetId()) + } + + return taskIds, nil +} + +func (svc *Service) getNodeIds(opts *interfaces.SpiderRunOptions) (nodeIds []primitive.ObjectID, err error) { + if opts.Mode == constants.RunTypeAllNodes { + query := bson.M{ + "active": true, + "enabled": true, + "status": constants.NodeStatusOnline, + } + nodes, err := svc.modelSvc.GetNodeList(query, nil) + if err != nil { + return nil, err + } + for _, node := range nodes { + nodeIds = append(nodeIds, node.GetId()) + } + } else if opts.Mode == constants.RunTypeSelectedNodes { + nodeIds = opts.NodeIds + } + return nodeIds, nil +} + +func (svc *Service) isMultiTask(opts *interfaces.SpiderRunOptions) (res bool) { + if opts.Mode == constants.RunTypeAllNodes { + query := bson.M{ + "active": true, + "enabled": true, + "status": constants.NodeStatusOnline, + } + nodes, err := svc.modelSvc.GetNodeList(query, nil) + if err != nil { + trace.PrintError(err) + return false + } + return len(nodes) > 1 + } else if opts.Mode == constants.RunTypeRandom { + return false + } else if opts.Mode == constants.RunTypeSelectedNodes { + return len(opts.NodeIds) > 1 + } else { + return false + } +} + +func (svc *Service) syncGit() { + if svc.syncLock { + log.Infof("[SpiderAdminService] sync git is locked, skip") + return + } + log.Infof("[SpiderAdminService] start to sync git") + + svc.syncLock = true + defer func() { + svc.syncLock = false + }() + + // spiders + spiders, err := svc.modelSvc.GetSpiderList(nil, nil) + if err != nil { + trace.PrintError(err) + return + } + + // spider ids + var spiderIds []primitive.ObjectID + for _, s := range spiders { + spiderIds = append(spiderIds, s.Id) + } + + if len(spiderIds) > 0 { + // gits + gits, err := svc.modelSvc.GetGitList(bson.M{ + "_id": bson.M{ + "$in": spiderIds, + }, + "auto_pull": true, + }, nil) + if err != nil { + trace.PrintError(err) + return + } + + wg := sync.WaitGroup{} + wg.Add(len(gits)) + for _, g := range gits { + go func(g models.Git) { + svc.syncGitOne(&g) + wg.Done() + }(g) + } + wg.Wait() + } + + log.Infof("[SpiderAdminService] finished sync git") +} + +func (svc *Service) syncGitOne(g interfaces.Git) { + log.Infof("[SpiderAdminService] sync git %s", g.GetId()) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + // git client + workspacePath := viper.GetString("workspace") + gitClient, err := vcs.NewGitClient(vcs.WithPath(filepath.Join(workspacePath, g.GetId().Hex()))) + if err != nil { + return + } + + // set auth + utils.InitGitClientAuth(g, gitClient) + + // check if remote has changes + ok, err := gitClient.IsRemoteChanged() + if err != nil { + trace.PrintError(err) + return + } + if !ok { + // no change + return + } + + // pull and sync to workspace + if err := gitClient.Reset(); err != nil { + trace.PrintError(err) + return + } + if err := gitClient.Pull(); err != nil { + trace.PrintError(err) + return + } + + // wait for context to end + <-ctx.Done() +} + +func NewSpiderAdminService(opts ...Option) (svc2 interfaces.SpiderAdminService, err error) { + svc := &Service{ + cfgPath: config2.GetConfigPath(), + } + + // apply options + for _, opt := range opts { + opt(svc) + } + + // dependency injection + if err := container.GetContainer().Invoke(func(nodeCfgSvc interfaces.NodeConfigService, modelSvc service.ModelService, schedulerSvc interfaces.TaskSchedulerService) { + svc.nodeCfgSvc = nodeCfgSvc + svc.modelSvc = modelSvc + svc.schedulerSvc = schedulerSvc + }); err != nil { + return nil, trace.TraceError(err) + } + + // cron + svc.cron = cron.New() + + // validate node type + if !svc.nodeCfgSvc.IsMaster() { + return nil, trace.TraceError(errors.ErrorSpiderForbidden) + } + + return svc, nil +} + +var _service interfaces.SpiderAdminService + +func GetSpiderAdminService() (svc2 interfaces.SpiderAdminService, err error) { + if _service != nil { + return _service, nil + } + + _service, err = NewSpiderAdminService() + if err != nil { + return nil, err + } + + return _service, nil +} diff --git a/core/spider/admin/service_v2.go b/core/spider/admin/service_v2.go new file mode 100644 index 00000000..73a84596 --- /dev/null +++ b/core/spider/admin/service_v2.go @@ -0,0 +1,328 @@ +package admin + +import ( + "context" + "github.com/apex/log" + vcs "github.com/crawlab-team/crawlab-vcs" + config2 "github.com/crawlab-team/crawlab/core/config" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/node/config" + "github.com/crawlab-team/crawlab/core/task/scheduler" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/google/uuid" + "github.com/robfig/cron/v3" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "os" + "path" + "path/filepath" + "sync" + "time" +) + +type ServiceV2 struct { + // dependencies + nodeCfgSvc interfaces.NodeConfigService + schedulerSvc *scheduler.ServiceV2 + cron *cron.Cron + syncLock bool + + // settings + cfgPath string +} + +func (svc *ServiceV2) Start() (err error) { + return svc.SyncGit() +} + +func (svc *ServiceV2) Schedule(id primitive.ObjectID, opts *interfaces.SpiderRunOptions) (taskIds []primitive.ObjectID, err error) { + // spider + s, err := service.NewModelServiceV2[models.SpiderV2]().GetById(id) + if err != nil { + return nil, err + } + + // assign tasks + return svc.scheduleTasks(s, opts) +} + +func (svc *ServiceV2) SyncGit() (err error) { + if _, err = svc.cron.AddFunc("* * * * *", svc.syncGit); err != nil { + return trace.TraceError(err) + } + svc.cron.Start() + return nil +} + +func (svc *ServiceV2) SyncGitOne(g *models.GitV2) (err error) { + svc.syncGitOne(g) + return nil +} + +func (svc *ServiceV2) Export(id primitive.ObjectID) (filePath string, err error) { + // spider fs + workspacePath := viper.GetString("workspace") + spiderFolderPath := filepath.Join(workspacePath, id.Hex()) + + // zip files in workspace + dirPath := spiderFolderPath + zipFilePath := path.Join(os.TempDir(), uuid.New().String()+".zip") + if err := utils.ZipDirectory(dirPath, zipFilePath); err != nil { + return "", trace.TraceError(err) + } + + return zipFilePath, nil +} + +func (svc *ServiceV2) scheduleTasks(s *models.SpiderV2, opts *interfaces.SpiderRunOptions) (taskIds []primitive.ObjectID, err error) { + // main task + mainTask := &models.TaskV2{ + SpiderId: s.Id, + Mode: opts.Mode, + NodeIds: opts.NodeIds, + Cmd: opts.Cmd, + Param: opts.Param, + ScheduleId: opts.ScheduleId, + Priority: opts.Priority, + UserId: opts.UserId, + CreateTs: time.Now(), + } + mainTask.SetId(primitive.NewObjectID()) + + // normalize + if mainTask.Mode == "" { + mainTask.Mode = s.Mode + } + if mainTask.NodeIds == nil { + mainTask.NodeIds = s.NodeIds + } + if mainTask.Cmd == "" { + mainTask.Cmd = s.Cmd + } + if mainTask.Param == "" { + mainTask.Param = s.Param + } + if mainTask.Priority == 0 { + mainTask.Priority = s.Priority + } + + if svc.isMultiTask(opts) { + // multi tasks + nodeIds, err := svc.getNodeIds(opts) + if err != nil { + return nil, err + } + for _, nodeId := range nodeIds { + t := &models.TaskV2{ + SpiderId: s.Id, + Mode: opts.Mode, + Cmd: opts.Cmd, + Param: opts.Param, + NodeId: nodeId, + ScheduleId: opts.ScheduleId, + Priority: opts.Priority, + UserId: opts.UserId, + CreateTs: time.Now(), + } + t.SetId(primitive.NewObjectID()) + t2, err := svc.schedulerSvc.Enqueue(t, opts.UserId) + if err != nil { + return nil, err + } + taskIds = append(taskIds, t2.Id) + } + } else { + // single task + nodeIds, err := svc.getNodeIds(opts) + if err != nil { + return nil, err + } + if len(nodeIds) > 0 { + mainTask.NodeId = nodeIds[0] + } + t2, err := svc.schedulerSvc.Enqueue(mainTask, opts.UserId) + if err != nil { + return nil, err + } + taskIds = append(taskIds, t2.Id) + } + + return taskIds, nil +} + +func (svc *ServiceV2) getNodeIds(opts *interfaces.SpiderRunOptions) (nodeIds []primitive.ObjectID, err error) { + if opts.Mode == constants.RunTypeAllNodes { + query := bson.M{ + "active": true, + "enabled": true, + "status": constants.NodeStatusOnline, + } + nodes, err := service.NewModelServiceV2[models.NodeV2]().GetMany(query, nil) + if err != nil { + return nil, err + } + for _, node := range nodes { + nodeIds = append(nodeIds, node.Id) + } + } else if opts.Mode == constants.RunTypeSelectedNodes { + nodeIds = opts.NodeIds + } + return nodeIds, nil +} + +func (svc *ServiceV2) isMultiTask(opts *interfaces.SpiderRunOptions) (res bool) { + if opts.Mode == constants.RunTypeAllNodes { + query := bson.M{ + "active": true, + "enabled": true, + "status": constants.NodeStatusOnline, + } + nodes, err := service.NewModelServiceV2[models.NodeV2]().GetMany(query, nil) + if err != nil { + trace.PrintError(err) + return false + } + return len(nodes) > 1 + } else if opts.Mode == constants.RunTypeRandom { + return false + } else if opts.Mode == constants.RunTypeSelectedNodes { + return len(opts.NodeIds) > 1 + } else { + return false + } +} + +func (svc *ServiceV2) syncGit() { + if svc.syncLock { + log.Infof("[SpiderAdminService] sync git is locked, skip") + return + } + log.Infof("[SpiderAdminService] start to sync git") + + svc.syncLock = true + defer func() { + svc.syncLock = false + }() + + // spiders + spiders, err := service.NewModelServiceV2[models.SpiderV2]().GetMany(nil, nil) + if err != nil { + trace.PrintError(err) + return + } + + // spider ids + var spiderIds []primitive.ObjectID + for _, s := range spiders { + spiderIds = append(spiderIds, s.Id) + } + + if len(spiderIds) > 0 { + // gits + gits, err := service.NewModelServiceV2[models.GitV2]().GetMany(bson.M{ + "_id": bson.M{ + "$in": spiderIds, + }, + "auto_pull": true, + }, nil) + if err != nil { + trace.PrintError(err) + return + } + + wg := sync.WaitGroup{} + wg.Add(len(gits)) + for _, g := range gits { + go func(g models.GitV2) { + svc.syncGitOne(&g) + wg.Done() + }(g) + } + wg.Wait() + } + + log.Infof("[SpiderAdminService] finished sync git") +} + +func (svc *ServiceV2) syncGitOne(g *models.GitV2) { + log.Infof("[SpiderAdminService] sync git %s", g.Id) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + + // git client + workspacePath := viper.GetString("workspace") + gitClient, err := vcs.NewGitClient(vcs.WithPath(filepath.Join(workspacePath, g.Id.Hex()))) + if err != nil { + return + } + + // set auth + utils.InitGitClientAuthV2(g, gitClient) + + // check if remote has changes + ok, err := gitClient.IsRemoteChanged() + if err != nil { + trace.PrintError(err) + return + } + if !ok { + // no change + return + } + + // pull and sync to workspace + if err := gitClient.Reset(); err != nil { + trace.PrintError(err) + return + } + if err := gitClient.Pull(); err != nil { + trace.PrintError(err) + return + } + + // wait for context to end + <-ctx.Done() +} + +func NewSpiderAdminServiceV2() (svc2 *ServiceV2, err error) { + svc := &ServiceV2{ + nodeCfgSvc: config.GetNodeConfigService(), + cfgPath: config2.GetConfigPath(), + } + svc.schedulerSvc, err = scheduler.GetTaskSchedulerServiceV2() + if err != nil { + return nil, err + } + + // cron + svc.cron = cron.New() + + // validate node type + if !svc.nodeCfgSvc.IsMaster() { + return nil, trace.TraceError(errors.ErrorSpiderForbidden) + } + + return svc, nil +} + +var svcV2 *ServiceV2 + +func GetSpiderAdminServiceV2() (svc2 *ServiceV2, err error) { + if svcV2 != nil { + return svcV2, nil + } + + svcV2, err = NewSpiderAdminServiceV2() + if err != nil { + return nil, err + } + + return svcV2, nil +} diff --git a/core/stats/options.go b/core/stats/options.go new file mode 100644 index 00000000..b40e16c3 --- /dev/null +++ b/core/stats/options.go @@ -0,0 +1,5 @@ +package stats + +import "github.com/crawlab-team/crawlab/core/interfaces" + +type Option func(svc interfaces.StatsService) diff --git a/core/stats/service.go b/core/stats/service.go new file mode 100644 index 00000000..17df1be9 --- /dev/null +++ b/core/stats/service.go @@ -0,0 +1,323 @@ +package stats + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/interfaces" + "go.mongodb.org/mongo-driver/bson" + mongo2 "go.mongodb.org/mongo-driver/mongo" +) + +type Service struct { +} + +func (svc *Service) GetOverviewStats(query bson.M) (data interface{}, err error) { + stats := bson.M{} + + // nodes + stats["nodes"], err = mongo.GetMongoCol(interfaces.ModelColNameNode).Count(bson.M{"active": true}) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["nodes"] = 0 + } + + // projects + stats["projects"], err = mongo.GetMongoCol(interfaces.ModelColNameProject).Count(nil) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["projects"] = 0 + } + + // spiders + stats["spiders"], err = mongo.GetMongoCol(interfaces.ModelColNameSpider).Count(nil) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["spiders"] = 0 + } + + // schedules + stats["schedules"], err = mongo.GetMongoCol(interfaces.ModelColNameSchedule).Count(nil) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["schedules"] = 0 + } + + // tasks + stats["tasks"], err = mongo.GetMongoCol(interfaces.ModelColNameTask).Count(nil) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["tasks"] = 0 + } + + // error tasks + stats["error_tasks"], err = mongo.GetMongoCol(interfaces.ModelColNameTask).Count(bson.M{"status": constants.TaskStatusError}) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["error_tasks"] = 0 + } + + // results + stats["results"], err = svc.getOverviewResults(query) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["results"] = 0 + } + + // users + stats["users"], err = mongo.GetMongoCol(interfaces.ModelColNameUser).Count(nil) + if err != nil { + if err.Error() != mongo2.ErrNoDocuments.Error() { + return nil, err + } + stats["users"] = 0 + } + + return stats, nil +} + +func (svc *Service) GetDailyStats(query bson.M) (data interface{}, err error) { + tasksStats, err := svc.getDailyTasksStats(query) + if err != nil { + return nil, err + } + return tasksStats, nil +} + +func (svc *Service) GetTaskStats(query bson.M) (data interface{}, err error) { + stats := bson.M{} + + // by status + stats["by_status"], err = svc.getTaskStatsByStatus(query) + if err != nil { + return nil, err + } + + // by node + stats["by_node"], err = svc.getTaskStatsByNode(query) + if err != nil { + return nil, err + } + + // by spider + stats["by_spider"], err = svc.getTaskStatsBySpider(query) + if err != nil { + return nil, err + } + + return stats, nil +} + +func (svc *Service) getDailyTasksStats(query bson.M) (data interface{}, err error) { + pipeline := mongo2.Pipeline{ + {{ + "$match", query, + }}, + {{ + "$addFields", + bson.M{ + "date": bson.M{ + "$dateToString": bson.M{ + "date": bson.M{"$toDate": "$_id"}, + "format": "%Y-%m-%d", + "timezone": "Asia/Shanghai", // TODO: parameterization + }, + }, + }, + }}, + {{ + "$group", + bson.M{ + "_id": "$date", + "tasks": bson.M{"$sum": 1}, + "results": bson.M{"$sum": "$result_count"}, + }, + }}, + {{ + "$sort", + bson.D{{"_id", 1}}, + }}, + } + var results []entity.StatsDailyItem + if err := mongo.GetMongoCol(interfaces.ModelColNameTaskStat).Aggregate(pipeline, nil).All(&results); err != nil { + return nil, err + } + return results, nil +} + +func (svc *Service) getOverviewResults(query bson.M) (data interface{}, err error) { + pipeline := mongo2.Pipeline{ + {{"$match", query}}, + {{ + "$group", + bson.M{ + "_id": nil, + "results": bson.M{"$sum": "$result_count"}, + }, + }}, + } + var res bson.M + if err := mongo.GetMongoCol(interfaces.ModelColNameTaskStat).Aggregate(pipeline, nil).One(&res); err != nil { + return nil, err + } + return res["results"], nil +} + +func (svc *Service) getTaskStatsByStatus(query bson.M) (data interface{}, err error) { + pipeline := mongo2.Pipeline{ + {{"$match", query}}, + {{ + "$group", + bson.M{ + "_id": "$status", + "tasks": bson.M{"$sum": 1}, + }, + }}, + {{ + "$project", + bson.M{ + "status": "$_id", + "tasks": "$tasks", + }, + }}, + } + var results []bson.M + if err := mongo.GetMongoCol(interfaces.ModelColNameTask).Aggregate(pipeline, nil).All(&results); err != nil { + return nil, err + } + return results, nil +} + +func (svc *Service) getTaskStatsByNode(query bson.M) (data interface{}, err error) { + pipeline := mongo2.Pipeline{ + {{"$match", query}}, + {{ + "$group", + bson.M{ + "_id": "$node_id", + "tasks": bson.M{"$sum": 1}, + }, + }}, + {{ + "$lookup", + bson.M{ + "from": interfaces.ModelColNameNode, + "localField": "_id", + "foreignField": "_id", + "as": "_n", + }, + }}, + {{ + "$project", + bson.M{ + "node_id": "$node_id", + "node": bson.M{"$arrayElemAt": bson.A{"$_n", 0}}, + "node_name": bson.M{"$arrayElemAt": bson.A{"$_n.name", 0}}, + "tasks": "$tasks", + }, + }}, + } + var results []bson.M + if err := mongo.GetMongoCol(interfaces.ModelColNameTask).Aggregate(pipeline, nil).All(&results); err != nil { + return nil, err + } + return results, nil +} + +func (svc *Service) getTaskStatsBySpider(query bson.M) (data interface{}, err error) { + pipeline := mongo2.Pipeline{ + {{"$match", query}}, + {{ + "$group", + bson.M{ + "_id": "$spider_id", + "tasks": bson.M{"$sum": 1}, + }, + }}, + {{ + "$lookup", + bson.M{ + "from": interfaces.ModelColNameSpider, + "localField": "_id", + "foreignField": "_id", + "as": "_s", + }, + }}, + {{ + "$project", + bson.M{ + "spider_id": "$spider_id", + "spider": bson.M{"$arrayElemAt": bson.A{"$_s", 0}}, + "spider_name": bson.M{"$arrayElemAt": bson.A{"$_s.name", 0}}, + "tasks": "$tasks", + }, + }}, + {{"$limit", 10}}, + } + var results []bson.M + if err := mongo.GetMongoCol(interfaces.ModelColNameTask).Aggregate(pipeline, nil).All(&results); err != nil { + return nil, err + } + return results, nil +} + +func (svc *Service) getTaskStatsHistogram(query bson.M) (data interface{}, err error) { + pipeline := mongo2.Pipeline{ + {{"$match", query}}, + {{ + "$lookup", + bson.M{ + "from": interfaces.ModelColNameTaskStat, + "localField": "_id", + "foreignField": "_id", + "as": "_ts", + }, + }}, + {{ + "$facet", + bson.M{ + "total_duration": bson.A{ + bson.M{ + "$bucketAuto": bson.M{ + "groupBy": "$_ts.td", + "buckets": 10, + "granularity": "1-2-5", + }, + }, + }, + }, + }}, + } + var res bson.M + if err := mongo.GetMongoCol(interfaces.ModelColNameTask).Aggregate(pipeline, nil).One(&res); err != nil { + return nil, err + } + return res, nil +} + +var svc interfaces.StatsService + +func GetStatsService() interfaces.StatsService { + if svc != nil { + return svc + } + + // service + svc = &Service{} + + return svc +} diff --git a/core/sys_exec/sys_exec.go b/core/sys_exec/sys_exec.go new file mode 100644 index 00000000..397e4357 --- /dev/null +++ b/core/sys_exec/sys_exec.go @@ -0,0 +1,89 @@ +package sys_exec + +import ( + "bufio" + "github.com/crawlab-team/go-trace" + "github.com/shirou/gopsutil/process" + "os/exec" + "time" +) + +type KillProcessOptions struct { + Timeout time.Duration + Force bool +} + +func KillProcess(cmd *exec.Cmd, opts *KillProcessOptions) error { + // process + p, err := process.NewProcess(int32(cmd.Process.Pid)) + if err != nil { + return err + } + + // kill function + killFunc := func(p *process.Process) error { + return killProcessRecursive(p, opts.Force) + } + + if opts.Timeout != 0 { + // with timeout + return killProcessWithTimeout(p, opts.Timeout, killFunc) + } else { + // without timeout + return killFunc(p) + } +} + +func killProcessWithTimeout(p *process.Process, timeout time.Duration, killFunc func(*process.Process) error) error { + go func() { + if err := killFunc(p); err != nil { + trace.PrintError(err) + } + }() + for i := 0; i < int(timeout.Seconds()); i++ { + ok, err := process.PidExists(p.Pid) + if err == nil && !ok { + return nil + } + time.Sleep(1 * time.Second) + } + return killProcess(p, true) +} + +func killProcessRecursive(p *process.Process, force bool) (err error) { + // children processes + cps, err := p.Children() + if err != nil { + return killProcess(p, force) + } + + // iterate children processes + for _, cp := range cps { + if err := killProcessRecursive(cp, force); err != nil { + return err + } + } + + return nil +} + +func killProcess(p *process.Process, force bool) (err error) { + if force { + err = p.Kill() + } else { + err = p.Terminate() + } + if err != nil { + return trace.TraceError(err) + } + return nil +} + +func ConfigureCmdLogging(cmd *exec.Cmd, fn func(scanner *bufio.Scanner)) { + stdout, _ := (*cmd).StdoutPipe() + stderr, _ := (*cmd).StderrPipe() + scannerStdout := bufio.NewScanner(stdout) + scannerStderr := bufio.NewScanner(stderr) + go fn(scannerStdout) + go fn(scannerStderr) +} diff --git a/core/sys_exec/sys_exec_darwin.go b/core/sys_exec/sys_exec_darwin.go new file mode 100644 index 00000000..130b7cb8 --- /dev/null +++ b/core/sys_exec/sys_exec_darwin.go @@ -0,0 +1,24 @@ +//go:build darwin +// +build darwin + +package sys_exec + +import ( + "os/exec" + "syscall" +) + +func BuildCmd(cmdStr string) *exec.Cmd { + return exec.Command("sh", "-c", cmdStr) +} + +func SetPgid(cmd *exec.Cmd) { + if cmd == nil { + return + } + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + } else { + cmd.SysProcAttr.Setpgid = true + } +} diff --git a/core/sys_exec/sys_exec_linux.go b/core/sys_exec/sys_exec_linux.go new file mode 100644 index 00000000..bf532a43 --- /dev/null +++ b/core/sys_exec/sys_exec_linux.go @@ -0,0 +1,24 @@ +//go:build linux +// +build linux + +package sys_exec + +import ( + "os/exec" + "syscall" +) + +func BuildCmd(cmdStr string) *exec.Cmd { + return exec.Command("sh", "-c", cmdStr) +} + +func SetPgid(cmd *exec.Cmd) { + if cmd == nil { + return + } + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + } else { + cmd.SysProcAttr.Setpgid = true + } +} diff --git a/core/sys_exec/sys_exec_windows.go b/core/sys_exec/sys_exec_windows.go new file mode 100644 index 00000000..e2678ccd --- /dev/null +++ b/core/sys_exec/sys_exec_windows.go @@ -0,0 +1,10 @@ +//go:build windows +// +build windows + +package sys_exec + +import "os/exec" + +func BuildCmd(cmdStr string) *exec.Cmd { + return exec.Command("cmd", "/C", cmdStr) +} diff --git a/core/system/service.go b/core/system/service.go new file mode 100644 index 00000000..10846740 --- /dev/null +++ b/core/system/service.go @@ -0,0 +1,86 @@ +package system + +import ( + mongo2 "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +type Service struct { + col *mongo2.Col + modelSvc service.ModelService +} + +func (svc *Service) Init() (err error) { + // initialize data + if err := svc.initData(); err != nil { + return err + } + + return nil +} + +func (svc *Service) initData() (err error) { + total, err := svc.col.Count(bson.M{ + "key": "site_title", + }) + if err != nil { + return err + } + if total > 0 { + return nil + } + + // data to initialize + settings := []models.Setting{ + { + Id: primitive.NewObjectID(), + Key: "site_title", + Value: bson.M{ + "customize_site_title": false, + "site_title": "", + }, + }, + } + var data []interface{} + for _, s := range settings { + data = append(data, s) + } + _, err = svc.col.InsertMany(data) + if err != nil { + return err + } + return nil +} + +func NewService() *Service { + // service + svc := &Service{ + col: mongo2.GetMongoCol(interfaces.ModelColNameSetting), + } + + // model service + modelSvc, err := service.GetService() + if err != nil { + panic(err) + } + svc.modelSvc = modelSvc + + if err := svc.Init(); err != nil { + panic(err) + } + + return svc +} + +var _service *Service + +func GetService() *Service { + if _service == nil { + _service = NewService() + } + return _service +} diff --git a/core/task/base.go b/core/task/base.go new file mode 100644 index 00000000..265186a3 --- /dev/null +++ b/core/task/base.go @@ -0,0 +1,97 @@ +package task + +import ( + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" +) + +type BaseService struct { + // dependencies + interfaces.WithConfigPath + modelSvc service.ModelService + + // internals + stopped bool +} + +func (svc *BaseService) Init() error { + // implement me + return nil +} + +func (svc *BaseService) Start() { + // implement me +} + +func (svc *BaseService) Wait() { + utils.DefaultWait() +} + +func (svc *BaseService) Stop() { + svc.stopped = true +} + +// SaveTask deprecated +func (svc *BaseService) SaveTask(t interfaces.Task, status string) (err error) { + // normalize status + if status == "" { + status = constants.TaskStatusPending + } + + // set task status + t.SetStatus(status) + + // attempt to get task from database + _, err = svc.modelSvc.GetTaskById(t.GetId()) + if err != nil { + // if task does not exist, add to database + if err == mongo.ErrNoDocuments { + if err := delegate.NewModelDelegate(t).Add(); err != nil { + return err + } + return nil + } else { + return err + } + } else { + // otherwise, update + if err := delegate.NewModelDelegate(t).Save(); err != nil { + return err + } + return nil + } +} + +func (svc *BaseService) IsStopped() (res bool) { + return svc.stopped +} + +func (svc *BaseService) GetQueue(nodeId primitive.ObjectID) (queue string) { + if nodeId.IsZero() { + return fmt.Sprintf("%s", constants.TaskListQueuePrefixPublic) + } else { + return fmt.Sprintf("%s:%s", constants.TaskListQueuePrefixNodes, nodeId.Hex()) + } +} + +func NewBaseService() (svc2 interfaces.TaskBaseService, err error) { + svc := &BaseService{} + + // dependency injection + if err := container.GetContainer().Invoke(func(cfgPath interfaces.WithConfigPath, modelSvc service.ModelService) { + svc.WithConfigPath = cfgPath + svc.modelSvc = modelSvc + }); err != nil { + return nil, trace.TraceError(err) + } + + return svc, nil +} diff --git a/core/task/handler/options.go b/core/task/handler/options.go new file mode 100644 index 00000000..c2c03558 --- /dev/null +++ b/core/task/handler/options.go @@ -0,0 +1,40 @@ +package handler + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "time" +) + +type Option func(svc interfaces.TaskHandlerService) + +func WithConfigPath(path string) Option { + return func(svc interfaces.TaskHandlerService) { + svc.SetConfigPath(path) + } +} + +func WithExitWatchDuration(duration time.Duration) Option { + return func(svc interfaces.TaskHandlerService) { + svc.SetExitWatchDuration(duration) + } +} + +func WithReportInterval(interval time.Duration) Option { + return func(svc interfaces.TaskHandlerService) { + svc.SetReportInterval(interval) + } +} + +func WithCancelTimeout(timeout time.Duration) Option { + return func(svc interfaces.TaskHandlerService) { + svc.SetCancelTimeout(timeout) + } +} + +type RunnerOption func(r interfaces.TaskRunner) + +func WithSubscribeTimeout(timeout time.Duration) RunnerOption { + return func(r interfaces.TaskRunner) { + r.SetSubscribeTimeout(timeout) + } +} diff --git a/core/task/handler/runner.go b/core/task/handler/runner.go new file mode 100644 index 00000000..11930910 --- /dev/null +++ b/core/task/handler/runner.go @@ -0,0 +1,691 @@ +package handler + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "github.com/apex/log" + "github.com/cenkalti/backoff/v4" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/errors" + fs2 "github.com/crawlab-team/crawlab/core/fs" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/client" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/sys_exec" + "github.com/crawlab-team/crawlab/core/utils" + grpc "github.com/crawlab-team/crawlab/grpc" + "github.com/crawlab-team/go-trace" + "github.com/shirou/gopsutil/process" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" +) + +type Runner struct { + // dependencies + svc interfaces.TaskHandlerService // task handler service + fsSvc interfaces.FsServiceV2 // task fs service + hookSvc interfaces.TaskHookService // task hook service + + // settings + subscribeTimeout time.Duration + bufferSize int + + // internals + cmd *exec.Cmd // process command instance + pid int // process id + tid primitive.ObjectID // task id + t interfaces.Task // task model.Task + s interfaces.Spider // spider model.Spider + ch chan constants.TaskSignal // channel to communicate between Service and Runner + err error // standard process error + envs []models.Env // environment variables + cwd string // working directory + c interfaces.GrpcClient // grpc client + sub grpc.TaskService_SubscribeClient // grpc task service stream client + + // log internals + scannerStdout *bufio.Reader + scannerStderr *bufio.Reader + logBatchSize int +} + +func (r *Runner) Init() (err error) { + // update task + if err := r.updateTask("", nil); err != nil { + return err + } + + // start grpc client + if !r.c.IsStarted() { + r.c.Start() + } + + // working directory + workspacePath := viper.GetString("workspace") + r.cwd = filepath.Join(workspacePath, r.s.GetId().Hex()) + + // sync files from master + if !utils.IsMaster() { + if err := r.syncFiles(); err != nil { + return err + } + } + + // grpc task service stream client + if err := r.initSub(); err != nil { + return err + } + + // pre actions + if r.hookSvc != nil { + if err := r.hookSvc.PreActions(r.t, r.s, r.fsSvc, r.svc); err != nil { + return err + } + } + + return nil +} + +func (r *Runner) Run() (err error) { + // log task started + log.Infof("task[%s] started", r.tid.Hex()) + + // configure cmd + r.configureCmd() + + // configure environment variables + r.configureEnv() + + // configure logging + r.configureLogging() + + // start process + if err := r.cmd.Start(); err != nil { + return r.updateTask(constants.TaskStatusError, err) + } + + // start logging + go r.startLogging() + + // process id + if r.cmd.Process == nil { + return r.updateTask(constants.TaskStatusError, constants.ErrNotExists) + } + r.pid = r.cmd.Process.Pid + r.t.SetPid(r.pid) + + // update task status (processing) + if err := r.updateTask(constants.TaskStatusRunning, nil); err != nil { + return err + } + + // wait for process to finish + go r.wait() + + // start health check + go r.startHealthCheck() + + // declare task status + status := "" + + // wait for signal + signal := <-r.ch + switch signal { + case constants.TaskSignalFinish: + err = nil + status = constants.TaskStatusFinished + case constants.TaskSignalCancel: + err = constants.ErrTaskCancelled + status = constants.TaskStatusCancelled + case constants.TaskSignalError: + err = r.err + status = constants.TaskStatusError + case constants.TaskSignalLost: + err = constants.ErrTaskLost + status = constants.TaskStatusError + default: + err = constants.ErrInvalidSignal + status = constants.TaskStatusError + } + + // update task status + if err := r.updateTask(status, err); err != nil { + return err + } + + // post actions + if r.hookSvc != nil { + if err := r.hookSvc.PostActions(r.t, r.s, r.fsSvc, r.svc); err != nil { + return err + } + } + + return err +} + +func (r *Runner) Cancel() (err error) { + // kill process + opts := &sys_exec.KillProcessOptions{ + Timeout: r.svc.GetCancelTimeout(), + Force: true, + } + if err := sys_exec.KillProcess(r.cmd, opts); err != nil { + return err + } + + // make sure the process does not exist + op := func() error { + if exists, _ := process.PidExists(int32(r.pid)); exists { + return errors.ErrorTaskProcessStillExists + } + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), r.svc.GetExitWatchDuration()) + defer cancel() + b := backoff.WithContext(backoff.NewConstantBackOff(1*time.Second), ctx) + if err := backoff.Retry(op, b); err != nil { + return trace.TraceError(errors.ErrorTaskUnableToCancel) + } + + return nil +} + +// CleanUp clean up task runner +func (r *Runner) CleanUp() (err error) { + return nil +} + +func (r *Runner) SetSubscribeTimeout(timeout time.Duration) { + r.subscribeTimeout = timeout +} + +func (r *Runner) GetTaskId() (id primitive.ObjectID) { + return r.tid +} + +func (r *Runner) configureCmd() { + var cmdStr string + + // customized spider + if r.t.GetCmd() == "" { + cmdStr = r.s.GetCmd() + } else { + cmdStr = r.t.GetCmd() + } + + // parameters + if r.t.GetParam() != "" { + cmdStr += " " + r.t.GetParam() + } else if r.s.GetParam() != "" { + cmdStr += " " + r.s.GetParam() + } + + // get cmd instance + r.cmd = sys_exec.BuildCmd(cmdStr) + + // set working directory + r.cmd.Dir = r.cwd + + // configure pgid to allow killing sub processes + //sys_exec.SetPgid(r.cmd) +} + +func (r *Runner) configureLogging() { + // set stdout reader + stdout, _ := r.cmd.StdoutPipe() + r.scannerStdout = bufio.NewReaderSize(stdout, r.bufferSize) + + // set stderr reader + stderr, _ := r.cmd.StderrPipe() + r.scannerStderr = bufio.NewReaderSize(stderr, r.bufferSize) +} + +func (r *Runner) startLogging() { + // start reading stdout + go r.startLoggingReaderStdout() + + // start reading stderr + go r.startLoggingReaderStderr() +} + +func (r *Runner) startLoggingReaderStdout() { + for { + line, err := r.scannerStdout.ReadString(byte('\n')) + if err != nil { + break + } + line = strings.TrimSuffix(line, "\n") + r.writeLogLines([]string{line}) + } +} + +func (r *Runner) startLoggingReaderStderr() { + for { + line, err := r.scannerStderr.ReadString(byte('\n')) + if err != nil { + break + } + line = strings.TrimSuffix(line, "\n") + r.writeLogLines([]string{line}) + } +} + +func (r *Runner) startHealthCheck() { + if r.cmd.ProcessState == nil || r.cmd.ProcessState.Exited() { + return + } + for { + exists, _ := process.PidExists(int32(r.pid)) + if !exists { + // process lost + r.ch <- constants.TaskSignalLost + return + } + time.Sleep(1 * time.Second) + } +} + +func (r *Runner) configureEnv() { + // 默认把Node.js的全局node_modules加入环境变量 + envPath := os.Getenv("PATH") + nodePath := "/usr/lib/node_modules" + if !strings.Contains(envPath, nodePath) { + _ = os.Setenv("PATH", nodePath+":"+envPath) + } + _ = os.Setenv("NODE_PATH", nodePath) + + // default envs + r.cmd.Env = append(os.Environ(), "CRAWLAB_TASK_ID="+r.tid.Hex()) + if viper.GetString("grpc.address") != "" { + r.cmd.Env = append(r.cmd.Env, "CRAWLAB_GRPC_ADDRESS="+viper.GetString("grpc.address")) + } + if viper.GetString("grpc.authKey") != "" { + r.cmd.Env = append(r.cmd.Env, "CRAWLAB_GRPC_AUTH_KEY="+viper.GetString("grpc.authKey")) + } else { + r.cmd.Env = append(r.cmd.Env, "CRAWLAB_GRPC_AUTH_KEY="+constants.DefaultGrpcAuthKey) + } + + // global environment variables + envs, err := r.svc.GetModelEnvironmentService().GetEnvironmentList(nil, nil) + if err != nil { + trace.PrintError(err) + return + } + for _, env := range envs { + r.cmd.Env = append(r.cmd.Env, env.GetKey()+"="+env.GetValue()) + } +} + +func (r *Runner) syncFiles() (err error) { + masterURL := fmt.Sprintf("%s/sync/%s", viper.GetString("api.endpoint"), r.s.GetId().Hex()) + workspacePath := viper.GetString("workspace") + workerDir := filepath.Join(workspacePath, r.s.GetId().Hex()) + + // get file list from master + resp, err := http.Get(masterURL + "/scan") + if err != nil { + fmt.Println("Error getting file list from master:", err) + return trace.TraceError(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response body:", err) + return trace.TraceError(err) + } + var masterFiles map[string]entity.FsFileInfo + err = json.Unmarshal(body, &masterFiles) + if err != nil { + fmt.Println("Error unmarshaling JSON:", err) + return trace.TraceError(err) + } + + // create a map for master files + masterFilesMap := make(map[string]entity.FsFileInfo) + for _, file := range masterFiles { + masterFilesMap[file.Path] = file + } + + // create worker directory if not exists + if _, err := os.Stat(workerDir); os.IsNotExist(err) { + if err := os.MkdirAll(workerDir, os.ModePerm); err != nil { + fmt.Println("Error creating worker directory:", err) + return trace.TraceError(err) + } + } + + // get file list from worker + workerFiles, err := utils.ScanDirectory(workerDir) + if err != nil { + fmt.Println("Error scanning worker directory:", err) + return trace.TraceError(err) + } + + // set up wait group and error channel + var wg sync.WaitGroup + errCh := make(chan error, 1) + + // delete files that are deleted on master node + for path, workerFile := range workerFiles { + if _, exists := masterFilesMap[path]; !exists { + fmt.Println("Deleting file:", path) + err := os.Remove(workerFile.FullPath) + if err != nil { + fmt.Println("Error deleting file:", err) + } + } + } + + // download files that are new or modified on master node + for path, masterFile := range masterFilesMap { + workerFile, exists := workerFiles[path] + if !exists || masterFile.Hash != workerFile.Hash { + wg.Add(1) + go func(path string, masterFile entity.FsFileInfo) { + defer wg.Done() + logrus.Infof("File needs to be synchronized: %s", path) + err := r.downloadFile(masterURL+"/download?path="+path, filepath.Join(workerDir, path)) + if err != nil { + logrus.Errorf("Error downloading file: %v", err) + select { + case errCh <- err: + default: + } + } + }(path, masterFile) + } + } + + wg.Wait() + close(errCh) + if err := <-errCh; err != nil { + return err + } + + return nil +} + +func (r *Runner) downloadFile(url string, filePath string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(filePath) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + return err +} + +// wait for process to finish and send task signal (constants.TaskSignal) +// to task runner's channel (Runner.ch) according to exit code +func (r *Runner) wait() { + // wait for process to finish + if err := r.cmd.Wait(); err != nil { + exitError, ok := err.(*exec.ExitError) + if !ok { + r.ch <- constants.TaskSignalError + return + } + exitCode := exitError.ExitCode() + if exitCode == -1 { + // cancel error + r.ch <- constants.TaskSignalCancel + return + } + + // standard error + r.err = err + r.ch <- constants.TaskSignalError + return + } + + // success + r.ch <- constants.TaskSignalFinish +} + +// updateTask update and get updated info of task (Runner.t) +func (r *Runner) updateTask(status string, e error) (err error) { + if r.t != nil && status != "" { + // update task status + r.t.SetStatus(status) + if e != nil { + r.t.SetError(e.Error()) + } + if r.svc.GetNodeConfigService().IsMaster() { + if err := delegate.NewModelDelegate(r.t).Save(); err != nil { + return err + } + } else { + if err := client.NewModelDelegate(r.t, client.WithDelegateConfigPath(r.svc.GetConfigPath())).Save(); err != nil { + return err + } + } + + // send notification + go r.sendNotification() + + // update stats + go func() { + r._updateTaskStat(status) + r._updateSpiderStat(status) + }() + } + + // get task + r.t, err = r.svc.GetTaskById(r.tid) + if err != nil { + return err + } + + return nil +} + +func (r *Runner) initSub() (err error) { + r.sub, err = r.c.GetTaskClient().Subscribe(context.Background()) + if err != nil { + return trace.TraceError(err) + } + return nil +} + +func (r *Runner) writeLogLines(lines []string) { + data, err := json.Marshal(&entity.StreamMessageTaskData{ + TaskId: r.tid, + Logs: lines, + }) + if err != nil { + trace.PrintError(err) + return + } + msg := &grpc.StreamMessage{ + Code: grpc.StreamMessageCode_INSERT_LOGS, + Data: data, + } + if err := r.sub.Send(msg); err != nil { + trace.PrintError(err) + return + } +} + +func (r *Runner) _updateTaskStat(status string) { + ts, err := r.svc.GetModelTaskStatService().GetTaskStatById(r.tid) + if err != nil { + trace.PrintError(err) + return + } + switch status { + case constants.TaskStatusPending: + // do nothing + case constants.TaskStatusRunning: + ts.SetStartTs(time.Now()) + ts.SetWaitDuration(ts.GetStartTs().Sub(ts.GetCreateTs()).Milliseconds()) + case constants.TaskStatusFinished, constants.TaskStatusError, constants.TaskStatusCancelled: + ts.SetEndTs(time.Now()) + ts.SetRuntimeDuration(ts.GetEndTs().Sub(ts.GetStartTs()).Milliseconds()) + ts.SetTotalDuration(ts.GetEndTs().Sub(ts.GetCreateTs()).Milliseconds()) + } + if r.svc.GetNodeConfigService().IsMaster() { + if err := delegate.NewModelDelegate(ts).Save(); err != nil { + trace.PrintError(err) + return + } + } else { + if err := client.NewModelDelegate(ts, client.WithDelegateConfigPath(r.svc.GetConfigPath())).Save(); err != nil { + trace.PrintError(err) + return + } + } +} + +func (r *Runner) sendNotification() { + data, err := json.Marshal(r.t) + if err != nil { + trace.PrintError(err) + return + } + req := &grpc.Request{ + NodeKey: r.svc.GetNodeConfigService().GetNodeKey(), + Data: data, + } + _, err = r.c.GetTaskClient().SendNotification(context.Background(), req) + if err != nil { + trace.PrintError(err) + return + } +} + +func (r *Runner) _updateSpiderStat(status string) { + // task stat + ts, err := r.svc.GetModelTaskStatService().GetTaskStatById(r.tid) + if err != nil { + trace.PrintError(err) + return + } + + // update + var update bson.M + switch status { + case constants.TaskStatusPending, constants.TaskStatusRunning: + update = bson.M{ + "$set": bson.M{ + "last_task_id": r.tid, // last task id + }, + "$inc": bson.M{ + "tasks": 1, // task count + "wait_duration": ts.GetWaitDuration(), // wait duration + }, + } + case constants.TaskStatusFinished, constants.TaskStatusError, constants.TaskStatusCancelled: + update = bson.M{ + "$inc": bson.M{ + "results": ts.GetResultCount(), // results + "runtime_duration": ts.GetRuntimeDuration() / 1000, // runtime duration + "total_duration": ts.GetTotalDuration() / 1000, // total duration + }, + } + default: + trace.PrintError(errors.ErrorTaskInvalidType) + return + } + + // perform update + if r.svc.GetNodeConfigService().IsMaster() { + if err := mongo.GetMongoCol(interfaces.ModelColNameSpiderStat).UpdateId(r.s.GetId(), update); err != nil { + trace.PrintError(err) + return + } + } else { + modelSvc, err := client.NewBaseServiceDelegate( + client.WithBaseServiceModelId(interfaces.ModelIdSpiderStat), + client.WithBaseServiceConfigPath(r.svc.GetConfigPath()), + ) + if err != nil { + trace.PrintError(err) + return + } + if err := modelSvc.UpdateById(r.s.GetId(), update); err != nil { + trace.PrintError(err) + return + } + } + +} + +func NewTaskRunner(id primitive.ObjectID, svc interfaces.TaskHandlerService, opts ...RunnerOption) (r2 interfaces.TaskRunner, err error) { + // validate options + if id.IsZero() { + return nil, constants.ErrInvalidOptions + } + + // runner + r := &Runner{ + subscribeTimeout: 30 * time.Second, + bufferSize: 1024 * 1024, + svc: svc, + tid: id, + ch: make(chan constants.TaskSignal), + logBatchSize: 20, + } + + // apply options + for _, opt := range opts { + opt(r) + } + + // task + r.t, err = svc.GetTaskById(id) + if err != nil { + return nil, err + } + + // spider + r.s, err = svc.GetSpiderById(r.t.GetSpiderId()) + if err != nil { + return nil, err + } + + // task fs service + r.fsSvc = fs2.NewFsServiceV2(filepath.Join(viper.GetString("workspace"), r.s.GetId().Hex())) + + // dependency injection + if err := container.GetContainer().Invoke(func( + c interfaces.GrpcClient, + ) { + r.c = c + }); err != nil { + return nil, trace.TraceError(err) + } + + _ = container.GetContainer().Invoke(func(hookSvc interfaces.TaskHookService) { + r.hookSvc = hookSvc + }) + + // initialize task runner + if err := r.Init(); err != nil { + return r, err + } + + return r, nil +} diff --git a/core/task/handler/runner_test.go b/core/task/handler/runner_test.go new file mode 100644 index 00000000..175117a3 --- /dev/null +++ b/core/task/handler/runner_test.go @@ -0,0 +1,103 @@ +package handler + +import ( + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/google/uuid" + "github.com/spf13/viper" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockRunner struct { + mock.Mock + Runner +} + +func (m *MockRunner) downloadFile(url string, filePath string) error { + args := m.Called(url, filePath) + return args.Error(0) +} + +func newMockRunner() *MockRunner { + r := &MockRunner{} + r.s = &models.Spider{} + return r +} + +func TestSyncFiles_SuccessWithDummyFiles(t *testing.T) { + // Create a test server that responds with a list of files + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/scan") { + w.Write([]byte(`{"file1.txt":{"path": "file1.txt", "hash": "hash1"}, "file2.txt":{"path": "file2.txt", "hash": "hash2"}}`)) + return + } + + if strings.HasSuffix(r.URL.Path, "/download") { + w.Write([]byte("file content")) + return + } + })) + defer ts.Close() + + // Create a mock runner + r := newMockRunner() + r.On("downloadFile", mock.Anything, mock.Anything).Return(nil) + + // Set the master URL to the test server URL + viper.Set("api.endpoint", ts.URL) + + localPath := filepath.Join(os.TempDir(), uuid.New().String()) + os.MkdirAll(filepath.Join(localPath, r.s.GetId().Hex()), os.ModePerm) + defer os.RemoveAll(localPath) + viper.Set("workspace", localPath) + + // Call the method under test + err := r.syncFiles() + assert.NoError(t, err) + + // Assert that the files were downloaded + assert.FileExists(t, filepath.Join(localPath, r.s.GetId().Hex(), "file1.txt")) + assert.FileExists(t, filepath.Join(localPath, r.s.GetId().Hex(), "file2.txt")) +} + +func TestSyncFiles_DeletesFilesNotOnMaster(t *testing.T) { + // Create a test server that responds with an empty list of files + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/scan") { + w.Write([]byte(`{}`)) + return + } + })) + defer ts.Close() + + // Create a mock runner + r := newMockRunner() + r.On("downloadFile", mock.Anything, mock.Anything).Return(nil) + + // Set the master URL to the test server URL + viper.Set("api.endpoint", ts.URL) + + localPath := filepath.Join(os.TempDir(), uuid.New().String()) + os.MkdirAll(filepath.Join(localPath, r.s.GetId().Hex()), os.ModePerm) + defer os.RemoveAll(localPath) + viper.Set("workspace", localPath) + + // Create a dummy file that should be deleted + dummyFilePath := filepath.Join(localPath, r.s.GetId().Hex(), "dummy.txt") + dummyFile, _ := os.Create(dummyFilePath) + dummyFile.Close() + + // Call the method under test + err := r.syncFiles() + assert.NoError(t, err) + + // Assert that the dummy file was deleted + assert.NoFileExists(t, dummyFilePath) +} diff --git a/core/task/handler/runner_v2.go b/core/task/handler/runner_v2.go new file mode 100644 index 00000000..5818f25c --- /dev/null +++ b/core/task/handler/runner_v2.go @@ -0,0 +1,671 @@ +package handler + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "github.com/apex/log" + "github.com/cenkalti/backoff/v4" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/crawlab/core/errors" + fs2 "github.com/crawlab-team/crawlab/core/fs" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/client" + "github.com/crawlab-team/crawlab/core/models/models" + service2 "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/sys_exec" + "github.com/crawlab-team/crawlab/core/utils" + grpc "github.com/crawlab-team/crawlab/grpc" + "github.com/crawlab-team/go-trace" + "github.com/shirou/gopsutil/process" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" +) + +type RunnerV2 struct { + // dependencies + svc *ServiceV2 // task handler service + fsSvc interfaces.FsServiceV2 // task fs service + + // settings + subscribeTimeout time.Duration + bufferSize int + + // internals + cmd *exec.Cmd // process command instance + pid int // process id + tid primitive.ObjectID // task id + t *models.TaskV2 // task model.Task + s *models.SpiderV2 // spider model.Spider + ch chan constants.TaskSignal // channel to communicate between Service and RunnerV2 + err error // standard process error + envs []models.Env // environment variables + cwd string // working directory + c interfaces.GrpcClient // grpc client + sub grpc.TaskService_SubscribeClient // grpc task service stream client + + // log internals + scannerStdout *bufio.Reader + scannerStderr *bufio.Reader + logBatchSize int +} + +func (r *RunnerV2) Init() (err error) { + // update task + if err := r.updateTask("", nil); err != nil { + return err + } + + // start grpc client + if !r.c.IsStarted() { + r.c.Start() + } + + // working directory + workspacePath := viper.GetString("workspace") + r.cwd = filepath.Join(workspacePath, r.s.Id.Hex()) + + // sync files from master + if !utils.IsMaster() { + if err := r.syncFiles(); err != nil { + return err + } + } + + // grpc task service stream client + if err := r.initSub(); err != nil { + return err + } + + return nil +} + +func (r *RunnerV2) Run() (err error) { + // log task started + log.Infof("task[%s] started", r.tid.Hex()) + + // configure cmd + r.configureCmd() + + // configure environment variables + r.configureEnv() + + // configure logging + r.configureLogging() + + // start process + if err := r.cmd.Start(); err != nil { + return r.updateTask(constants.TaskStatusError, err) + } + + // start logging + go r.startLogging() + + // process id + if r.cmd.Process == nil { + return r.updateTask(constants.TaskStatusError, constants.ErrNotExists) + } + r.pid = r.cmd.Process.Pid + r.t.Pid = r.pid + + // update task status (processing) + if err := r.updateTask(constants.TaskStatusRunning, nil); err != nil { + return err + } + + // wait for process to finish + go r.wait() + + // start health check + go r.startHealthCheck() + + // declare task status + status := "" + + // wait for signal + signal := <-r.ch + switch signal { + case constants.TaskSignalFinish: + err = nil + status = constants.TaskStatusFinished + case constants.TaskSignalCancel: + err = constants.ErrTaskCancelled + status = constants.TaskStatusCancelled + case constants.TaskSignalError: + err = r.err + status = constants.TaskStatusError + case constants.TaskSignalLost: + err = constants.ErrTaskLost + status = constants.TaskStatusError + default: + err = constants.ErrInvalidSignal + status = constants.TaskStatusError + } + + // update task status + if err := r.updateTask(status, err); err != nil { + return err + } + + return err +} + +func (r *RunnerV2) Cancel() (err error) { + // kill process + opts := &sys_exec.KillProcessOptions{ + Timeout: r.svc.GetCancelTimeout(), + Force: true, + } + if err := sys_exec.KillProcess(r.cmd, opts); err != nil { + return err + } + + // make sure the process does not exist + op := func() error { + if exists, _ := process.PidExists(int32(r.pid)); exists { + return errors.ErrorTaskProcessStillExists + } + return nil + } + ctx, cancel := context.WithTimeout(context.Background(), r.svc.GetExitWatchDuration()) + defer cancel() + b := backoff.WithContext(backoff.NewConstantBackOff(1*time.Second), ctx) + if err := backoff.Retry(op, b); err != nil { + return trace.TraceError(errors.ErrorTaskUnableToCancel) + } + + return nil +} + +// CleanUp clean up task runner +func (r *RunnerV2) CleanUp() (err error) { + return nil +} + +func (r *RunnerV2) SetSubscribeTimeout(timeout time.Duration) { + r.subscribeTimeout = timeout +} + +func (r *RunnerV2) GetTaskId() (id primitive.ObjectID) { + return r.tid +} + +func (r *RunnerV2) configureCmd() { + var cmdStr string + + // customized spider + if r.t.Cmd == "" { + cmdStr = r.s.Cmd + } else { + cmdStr = r.t.Cmd + } + + // parameters + if r.t.Param != "" { + cmdStr += " " + r.t.Param + } else if r.s.Param != "" { + cmdStr += " " + r.s.Param + } + + // get cmd instance + r.cmd = sys_exec.BuildCmd(cmdStr) + + // set working directory + r.cmd.Dir = r.cwd + + // configure pgid to allow killing sub processes + //sys_exec.SetPgid(r.cmd) +} + +func (r *RunnerV2) configureLogging() { + // set stdout reader + stdout, _ := r.cmd.StdoutPipe() + r.scannerStdout = bufio.NewReaderSize(stdout, r.bufferSize) + + // set stderr reader + stderr, _ := r.cmd.StderrPipe() + r.scannerStderr = bufio.NewReaderSize(stderr, r.bufferSize) +} + +func (r *RunnerV2) startLogging() { + // start reading stdout + go r.startLoggingReaderStdout() + + // start reading stderr + go r.startLoggingReaderStderr() +} + +func (r *RunnerV2) startLoggingReaderStdout() { + for { + line, err := r.scannerStdout.ReadString(byte('\n')) + if err != nil { + break + } + line = strings.TrimSuffix(line, "\n") + r.writeLogLines([]string{line}) + } +} + +func (r *RunnerV2) startLoggingReaderStderr() { + for { + line, err := r.scannerStderr.ReadString(byte('\n')) + if err != nil { + break + } + line = strings.TrimSuffix(line, "\n") + r.writeLogLines([]string{line}) + } +} + +func (r *RunnerV2) startHealthCheck() { + if r.cmd.ProcessState == nil || r.cmd.ProcessState.Exited() { + return + } + for { + exists, _ := process.PidExists(int32(r.pid)) + if !exists { + // process lost + r.ch <- constants.TaskSignalLost + return + } + time.Sleep(1 * time.Second) + } +} + +func (r *RunnerV2) configureEnv() { + // 默认把Node.js的全局node_modules加入环境变量 + envPath := os.Getenv("PATH") + nodePath := "/usr/lib/node_modules" + if !strings.Contains(envPath, nodePath) { + _ = os.Setenv("PATH", nodePath+":"+envPath) + } + _ = os.Setenv("NODE_PATH", nodePath) + + // default envs + r.cmd.Env = append(os.Environ(), "CRAWLAB_TASK_ID="+r.tid.Hex()) + if viper.GetString("grpc.address") != "" { + r.cmd.Env = append(r.cmd.Env, "CRAWLAB_GRPC_ADDRESS="+viper.GetString("grpc.address")) + } + if viper.GetString("grpc.authKey") != "" { + r.cmd.Env = append(r.cmd.Env, "CRAWLAB_GRPC_AUTH_KEY="+viper.GetString("grpc.authKey")) + } else { + r.cmd.Env = append(r.cmd.Env, "CRAWLAB_GRPC_AUTH_KEY="+constants.DefaultGrpcAuthKey) + } + + // global environment variables + envs, err := client.NewModelServiceV2[models.EnvironmentV2]().GetMany(nil, nil) + if err != nil { + trace.PrintError(err) + return + } + for _, env := range envs { + r.cmd.Env = append(r.cmd.Env, env.Key+"="+env.Value) + } +} + +func (r *RunnerV2) syncFiles() (err error) { + masterURL := fmt.Sprintf("%s/sync/%s", viper.GetString("api.endpoint"), r.s.Id.Hex()) + workspacePath := viper.GetString("workspace") + workerDir := filepath.Join(workspacePath, r.s.Id.Hex()) + + // get file list from master + resp, err := http.Get(masterURL + "/scan") + if err != nil { + fmt.Println("Error getting file list from master:", err) + return trace.TraceError(err) + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Println("Error reading response body:", err) + return trace.TraceError(err) + } + var masterFiles map[string]entity.FsFileInfo + err = json.Unmarshal(body, &masterFiles) + if err != nil { + fmt.Println("Error unmarshaling JSON:", err) + return trace.TraceError(err) + } + + // create a map for master files + masterFilesMap := make(map[string]entity.FsFileInfo) + for _, file := range masterFiles { + masterFilesMap[file.Path] = file + } + + // create worker directory if not exists + if _, err := os.Stat(workerDir); os.IsNotExist(err) { + if err := os.MkdirAll(workerDir, os.ModePerm); err != nil { + fmt.Println("Error creating worker directory:", err) + return trace.TraceError(err) + } + } + + // get file list from worker + workerFiles, err := utils.ScanDirectory(workerDir) + if err != nil { + fmt.Println("Error scanning worker directory:", err) + return trace.TraceError(err) + } + + // set up wait group and error channel + var wg sync.WaitGroup + errCh := make(chan error, 1) + + // delete files that are deleted on master node + for path, workerFile := range workerFiles { + if _, exists := masterFilesMap[path]; !exists { + fmt.Println("Deleting file:", path) + err := os.Remove(workerFile.FullPath) + if err != nil { + fmt.Println("Error deleting file:", err) + } + } + } + + // download files that are new or modified on master node + for path, masterFile := range masterFilesMap { + workerFile, exists := workerFiles[path] + if !exists || masterFile.Hash != workerFile.Hash { + wg.Add(1) + go func(path string, masterFile entity.FsFileInfo) { + defer wg.Done() + logrus.Infof("File needs to be synchronized: %s", path) + err := r.downloadFile(masterURL+"/download?path="+path, filepath.Join(workerDir, path)) + if err != nil { + logrus.Errorf("Error downloading file: %v", err) + select { + case errCh <- err: + default: + } + } + }(path, masterFile) + } + } + + wg.Wait() + close(errCh) + if err := <-errCh; err != nil { + return err + } + + return nil +} + +func (r *RunnerV2) downloadFile(url string, filePath string) error { + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(filePath) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, resp.Body) + return err +} + +// wait for process to finish and send task signal (constants.TaskSignal) +// to task runner's channel (RunnerV2.ch) according to exit code +func (r *RunnerV2) wait() { + // wait for process to finish + if err := r.cmd.Wait(); err != nil { + exitError, ok := err.(*exec.ExitError) + if !ok { + r.ch <- constants.TaskSignalError + return + } + exitCode := exitError.ExitCode() + if exitCode == -1 { + // cancel error + r.ch <- constants.TaskSignalCancel + return + } + + // standard error + r.err = err + r.ch <- constants.TaskSignalError + return + } + + // success + r.ch <- constants.TaskSignalFinish +} + +// updateTask update and get updated info of task (RunnerV2.t) +func (r *RunnerV2) updateTask(status string, e error) (err error) { + if r.t != nil && status != "" { + // update task status + r.t.Status = status + if e != nil { + r.t.Error = e.Error() + } + if r.svc.GetNodeConfigService().IsMaster() { + err = service2.NewModelServiceV2[models.TaskV2]().ReplaceById(r.t.Id, *r.t) + if err != nil { + return err + } + } else { + err = client.NewModelServiceV2[models.TaskV2]().ReplaceById(r.t.Id, *r.t) + if err != nil { + return err + } + } + + // send notification + go r.sendNotification() + + // update stats + go func() { + r._updateTaskStat(status) + r._updateSpiderStat(status) + }() + } + + // get task + r.t, err = r.svc.GetTaskById(r.tid) + if err != nil { + return err + } + + return nil +} + +func (r *RunnerV2) initSub() (err error) { + r.sub, err = r.c.GetTaskClient().Subscribe(context.Background()) + if err != nil { + return trace.TraceError(err) + } + return nil +} + +func (r *RunnerV2) writeLogLines(lines []string) { + data, err := json.Marshal(&entity.StreamMessageTaskData{ + TaskId: r.tid, + Logs: lines, + }) + if err != nil { + trace.PrintError(err) + return + } + msg := &grpc.StreamMessage{ + Code: grpc.StreamMessageCode_INSERT_LOGS, + Data: data, + } + if err := r.sub.Send(msg); err != nil { + trace.PrintError(err) + return + } +} + +func (r *RunnerV2) _updateTaskStat(status string) { + ts, err := client.NewModelServiceV2[models.TaskStatV2]().GetById(r.tid) + if err != nil { + trace.PrintError(err) + return + } + switch status { + case constants.TaskStatusPending: + // do nothing + case constants.TaskStatusRunning: + ts.StartTs = time.Now() + ts.WaitDuration = ts.StartTs.Sub(ts.CreateTs).Milliseconds() + case constants.TaskStatusFinished, constants.TaskStatusError, constants.TaskStatusCancelled: + if ts.StartTs.IsZero() { + ts.StartTs = time.Now() + ts.WaitDuration = ts.StartTs.Sub(ts.CreateTs).Milliseconds() + } + ts.EndTs = time.Now() + ts.RuntimeDuration = ts.EndTs.Sub(ts.StartTs).Milliseconds() + ts.TotalDuration = ts.EndTs.Sub(ts.CreateTs).Milliseconds() + } + if r.svc.GetNodeConfigService().IsMaster() { + err = service2.NewModelServiceV2[models.TaskStatV2]().ReplaceById(ts.Id, *ts) + if err != nil { + trace.PrintError(err) + return + } + } else { + err = client.NewModelServiceV2[models.TaskStatV2]().ReplaceById(ts.Id, *ts) + if err != nil { + trace.PrintError(err) + return + } + } +} + +func (r *RunnerV2) sendNotification() { + data, err := json.Marshal(r.t) + if err != nil { + trace.PrintError(err) + return + } + req := &grpc.Request{ + NodeKey: r.svc.GetNodeConfigService().GetNodeKey(), + Data: data, + } + _, err = r.c.GetTaskClient().SendNotification(context.Background(), req) + if err != nil { + trace.PrintError(err) + return + } +} + +func (r *RunnerV2) _updateSpiderStat(status string) { + // task stat + ts, err := client.NewModelServiceV2[models.TaskStatV2]().GetById(r.tid) + if err != nil { + trace.PrintError(err) + return + } + + // update + var update bson.M + switch status { + case constants.TaskStatusPending, constants.TaskStatusRunning: + update = bson.M{ + "$set": bson.M{ + "last_task_id": r.tid, // last task id + }, + "$inc": bson.M{ + "tasks": 1, // task count + "wait_duration": ts.WaitDuration, // wait duration + }, + } + case constants.TaskStatusFinished, constants.TaskStatusError, constants.TaskStatusCancelled: + update = bson.M{ + "$set": bson.M{ + "last_task_id": r.tid, // last task id + }, + "$inc": bson.M{ + "results": ts.ResultCount, // results + "runtime_duration": ts.RuntimeDuration / 1000, // runtime duration + "total_duration": ts.TotalDuration / 1000, // total duration + }, + } + default: + trace.PrintError(errors.ErrorTaskInvalidType) + return + } + + // perform update + if r.svc.GetNodeConfigService().IsMaster() { + err = service2.NewModelServiceV2[models.SpiderStatV2]().UpdateById(r.s.Id, update) + if err != nil { + trace.PrintError(err) + return + } + } else { + err = client.NewModelServiceV2[models.SpiderStatV2]().UpdateById(r.s.Id, update) + if err != nil { + trace.PrintError(err) + return + } + } + +} + +func NewTaskRunnerV2(id primitive.ObjectID, svc *ServiceV2) (r2 *RunnerV2, err error) { + // validate options + if id.IsZero() { + return nil, constants.ErrInvalidOptions + } + + // runner + r := &RunnerV2{ + subscribeTimeout: 30 * time.Second, + bufferSize: 1024 * 1024, + svc: svc, + tid: id, + ch: make(chan constants.TaskSignal), + logBatchSize: 20, + } + + // task + r.t, err = svc.GetTaskById(id) + if err != nil { + return nil, err + } + + // spider + r.s, err = svc.GetSpiderById(r.t.SpiderId) + if err != nil { + return nil, err + } + + // task fs service + r.fsSvc = fs2.NewFsServiceV2(filepath.Join(viper.GetString("workspace"), r.s.Id.Hex())) + + // dependency injection + if err := container.GetContainer().Invoke(func( + c interfaces.GrpcClient, + ) { + r.c = c + }); err != nil { + return nil, trace.TraceError(err) + } + + // initialize task runner + if err := r.Init(); err != nil { + return r, err + } + + return r, nil +} diff --git a/core/task/handler/service.go b/core/task/handler/service.go new file mode 100644 index 00000000..84a929ad --- /dev/null +++ b/core/task/handler/service.go @@ -0,0 +1,506 @@ +package handler + +import ( + "context" + "encoding/json" + "errors" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + errors2 "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/client" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/task" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "sync" + "time" +) + +type Service struct { + // dependencies + interfaces.TaskBaseService + cfgSvc interfaces.NodeConfigService + modelSvc service.ModelService + clientModelSvc interfaces.GrpcClientModelService + clientModelNodeSvc interfaces.GrpcClientModelNodeService + clientModelSpiderSvc interfaces.GrpcClientModelSpiderService + clientModelTaskSvc interfaces.GrpcClientModelTaskService + clientModelTaskStatSvc interfaces.GrpcClientModelTaskStatService + clientModelEnvironmentSvc interfaces.GrpcClientModelEnvironmentService + c interfaces.GrpcClient // grpc client + + // settings + //maxRunners int + exitWatchDuration time.Duration + reportInterval time.Duration + fetchInterval time.Duration + fetchTimeout time.Duration + cancelTimeout time.Duration + + // internals variables + stopped bool + mu sync.Mutex + runners sync.Map // pool of task runners started + syncLocks sync.Map // files sync locks map of task runners +} + +func (svc *Service) Start() { + // Initialize gRPC if not started + if !svc.c.IsStarted() { + svc.c.Start() + } + + go svc.ReportStatus() + go svc.Fetch() +} + +func (svc *Service) Run(taskId primitive.ObjectID) (err error) { + return svc.run(taskId) +} + +func (svc *Service) Reset() { + svc.mu.Lock() + defer svc.mu.Unlock() +} + +func (svc *Service) Cancel(taskId primitive.ObjectID) (err error) { + r, err := svc.getRunner(taskId) + if err != nil { + return err + } + if err := r.Cancel(); err != nil { + return err + } + return nil +} + +func (svc *Service) Fetch() { + for { + // wait + time.Sleep(svc.fetchInterval) + + // current node + n, err := svc.GetCurrentNode() + if err != nil { + continue + } + + // skip if node is not active or enabled + if !n.GetActive() || !n.GetEnabled() { + continue + } + + // validate if there are available runners + if svc.getRunnerCount() >= n.GetMaxRunners() { + continue + } + + // stop + if svc.stopped { + return + } + + // fetch task + tid, err := svc.fetch() + if err != nil { + trace.PrintError(err) + continue + } + + // skip if no task id + if tid.IsZero() { + continue + } + + // run task + if err := svc.run(tid); err != nil { + trace.PrintError(err) + t, err := svc.GetTaskById(tid) + if err == nil && t.GetStatus() != constants.TaskStatusCancelled { + t.SetError(err.Error()) + _ = svc.SaveTask(t, constants.TaskStatusError) + continue + } + continue + } + } +} + +func (svc *Service) ReportStatus() { + for { + if svc.stopped { + return + } + + // report handler status + if err := svc.reportStatus(); err != nil { + trace.PrintError(err) + } + + // wait + time.Sleep(svc.reportInterval) + } +} + +func (svc *Service) IsSyncLocked(path string) (ok bool) { + _, ok = svc.syncLocks.Load(path) + return ok +} + +func (svc *Service) LockSync(path string) { + svc.syncLocks.Store(path, true) +} + +func (svc *Service) UnlockSync(path string) { + svc.syncLocks.Delete(path) +} + +//func (svc *Service) GetMaxRunners() (maxRunners int) { +// return svc.maxRunners +//} +// +//func (svc *Service) SetMaxRunners(maxRunners int) { +// svc.maxRunners = maxRunners +//} + +func (svc *Service) GetExitWatchDuration() (duration time.Duration) { + return svc.exitWatchDuration +} + +func (svc *Service) SetExitWatchDuration(duration time.Duration) { + svc.exitWatchDuration = duration +} + +func (svc *Service) GetFetchInterval() (interval time.Duration) { + return svc.fetchInterval +} + +func (svc *Service) SetFetchInterval(interval time.Duration) { + svc.fetchInterval = interval +} + +func (svc *Service) GetReportInterval() (interval time.Duration) { + return svc.reportInterval +} + +func (svc *Service) SetReportInterval(interval time.Duration) { + svc.reportInterval = interval +} + +func (svc *Service) GetCancelTimeout() (timeout time.Duration) { + return svc.cancelTimeout +} + +func (svc *Service) SetCancelTimeout(timeout time.Duration) { + svc.cancelTimeout = timeout +} + +func (svc *Service) GetModelService() (modelSvc interfaces.GrpcClientModelService) { + return svc.clientModelSvc +} + +func (svc *Service) GetModelSpiderService() (modelSpiderSvc interfaces.GrpcClientModelSpiderService) { + return svc.clientModelSpiderSvc +} + +func (svc *Service) GetModelTaskService() (modelTaskSvc interfaces.GrpcClientModelTaskService) { + return svc.clientModelTaskSvc +} + +func (svc *Service) GetModelTaskStatService() (modelTaskSvc interfaces.GrpcClientModelTaskStatService) { + return svc.clientModelTaskStatSvc +} + +func (svc *Service) GetModelEnvironmentService() (modelTaskSvc interfaces.GrpcClientModelEnvironmentService) { + return svc.clientModelEnvironmentSvc +} + +func (svc *Service) GetNodeConfigService() (cfgSvc interfaces.NodeConfigService) { + return svc.cfgSvc +} + +func (svc *Service) GetCurrentNode() (n interfaces.Node, err error) { + // node key + nodeKey := svc.cfgSvc.GetNodeKey() + + // current node + if svc.cfgSvc.IsMaster() { + n, err = svc.modelSvc.GetNodeByKey(nodeKey, nil) + } else { + n, err = svc.clientModelNodeSvc.GetNodeByKey(nodeKey) + } + if err != nil { + return nil, err + } + + return n, nil +} + +func (svc *Service) GetTaskById(id primitive.ObjectID) (t interfaces.Task, err error) { + if svc.cfgSvc.IsMaster() { + t, err = svc.modelSvc.GetTaskById(id) + } else { + t, err = svc.clientModelTaskSvc.GetTaskById(id) + } + if err != nil { + return nil, err + } + + return t, nil +} + +func (svc *Service) GetSpiderById(id primitive.ObjectID) (s interfaces.Spider, err error) { + if svc.cfgSvc.IsMaster() { + s, err = svc.modelSvc.GetSpiderById(id) + } else { + s, err = svc.clientModelSpiderSvc.GetSpiderById(id) + } + if err != nil { + return nil, err + } + + return s, nil +} + +func (svc *Service) getRunners() (runners []*Runner) { + svc.mu.Lock() + defer svc.mu.Unlock() + svc.runners.Range(func(key, value interface{}) bool { + r := value.(Runner) + runners = append(runners, &r) + return true + }) + return runners +} + +func (svc *Service) getRunnerCount() (count int) { + n, err := svc.GetCurrentNode() + if err != nil { + trace.PrintError(err) + return + } + query := bson.M{ + "node_id": n.GetId(), + "status": constants.TaskStatusRunning, + } + if svc.cfgSvc.IsMaster() { + count, err = svc.modelSvc.GetBaseService(interfaces.ModelIdTask).Count(query) + if err != nil { + trace.PrintError(err) + return + } + } else { + count, err = svc.clientModelTaskSvc.Count(query) + if err != nil { + trace.PrintError(err) + return + } + } + return count +} + +func (svc *Service) getRunner(taskId primitive.ObjectID) (r interfaces.TaskRunner, err error) { + log.Debugf("[TaskHandlerService] getRunner: taskId[%v]", taskId) + v, ok := svc.runners.Load(taskId) + if !ok { + return nil, trace.TraceError(errors2.ErrorTaskNotExists) + } + switch v.(type) { + case interfaces.TaskRunner: + r = v.(interfaces.TaskRunner) + default: + return nil, trace.TraceError(errors2.ErrorModelInvalidType) + } + return r, nil +} + +func (svc *Service) addRunner(taskId primitive.ObjectID, r interfaces.TaskRunner) { + log.Debugf("[TaskHandlerService] addRunner: taskId[%v]", taskId) + svc.runners.Store(taskId, r) +} + +func (svc *Service) deleteRunner(taskId primitive.ObjectID) { + log.Debugf("[TaskHandlerService] deleteRunner: taskId[%v]", taskId) + svc.runners.Delete(taskId) +} + +func (svc *Service) saveTask(t interfaces.Task, status string) (err error) { + // normalize status + if status == "" { + status = constants.TaskStatusPending + } + + // set task status + t.SetStatus(status) + + // attempt to get task from database + _, err = svc.clientModelTaskSvc.GetTaskById(t.GetId()) + if err != nil { + // if task does not exist, add to database + if err == mongo.ErrNoDocuments { + if err := client.NewModelDelegate(t, client.WithDelegateConfigPath(svc.GetConfigPath())).Add(); err != nil { + return err + } + return nil + } else { + return err + } + } else { + // otherwise, update + if err := client.NewModelDelegate(t, client.WithDelegateConfigPath(svc.GetConfigPath())).Save(); err != nil { + return err + } + return nil + } +} + +func (svc *Service) reportStatus() (err error) { + // current node + n, err := svc.GetCurrentNode() + if err != nil { + return err + } + + // available runners of handler + ar := n.GetMaxRunners() - svc.getRunnerCount() + + // set available runners + n.SetAvailableRunners(ar) + + // save node + if svc.cfgSvc.IsMaster() { + err = delegate.NewModelDelegate(n).Save() + } else { + err = client.NewModelDelegate(n, client.WithDelegateConfigPath(svc.GetConfigPath())).Save() + } + if err != nil { + return err + } + + return nil +} + +func (svc *Service) fetch() (tid primitive.ObjectID, err error) { + ctx, cancel := context.WithTimeout(context.Background(), svc.fetchTimeout) + defer cancel() + res, err := svc.c.GetTaskClient().Fetch(ctx, svc.c.NewRequest(nil)) + if err != nil { + return tid, trace.TraceError(err) + } + if err := json.Unmarshal(res.Data, &tid); err != nil { + return tid, trace.TraceError(err) + } + return tid, nil +} + +func (svc *Service) run(taskId primitive.ObjectID) (err error) { + // attempt to get runner from pool + _, ok := svc.runners.Load(taskId) + if ok { + return trace.TraceError(errors2.ErrorTaskAlreadyExists) + } + + // create a new task runner + r, err := NewTaskRunner(taskId, svc) + if err != nil { + return trace.TraceError(err) + } + + // add runner to pool + svc.addRunner(taskId, r) + + // create a goroutine to run task + go func() { + // delete runner from pool + defer svc.deleteRunner(r.GetTaskId()) + defer func(r interfaces.TaskRunner) { + err := r.CleanUp() + if err != nil { + log.Errorf("task[%s] clean up error: %v", r.GetTaskId().Hex(), err) + } + }(r) + // run task process (blocking) + // error or finish after task runner ends + if err := r.Run(); err != nil { + switch { + case errors.Is(err, constants.ErrTaskError): + log.Errorf("task[%s] finished with error: %v", r.GetTaskId().Hex(), err) + case errors.Is(err, constants.ErrTaskCancelled): + log.Errorf("task[%s] cancelled", r.GetTaskId().Hex()) + default: + log.Errorf("task[%s] finished with unknown error: %v", r.GetTaskId().Hex(), err) + } + } + log.Infof("task[%s] finished", r.GetTaskId().Hex()) + }() + + return nil +} + +func NewTaskHandlerService() (svc2 interfaces.TaskHandlerService, err error) { + // base service + baseSvc, err := task.NewBaseService() + if err != nil { + return nil, trace.TraceError(err) + } + + // service + svc := &Service{ + TaskBaseService: baseSvc, + exitWatchDuration: 60 * time.Second, + fetchInterval: 1 * time.Second, + fetchTimeout: 15 * time.Second, + reportInterval: 5 * time.Second, + cancelTimeout: 5 * time.Second, + mu: sync.Mutex{}, + runners: sync.Map{}, + syncLocks: sync.Map{}, + } + + // dependency injection + if err := container.GetContainer().Invoke(func( + cfgSvc interfaces.NodeConfigService, + modelSvc service.ModelService, + clientModelSvc interfaces.GrpcClientModelService, + clientModelNodeSvc interfaces.GrpcClientModelNodeService, + clientModelSpiderSvc interfaces.GrpcClientModelSpiderService, + clientModelTaskSvc interfaces.GrpcClientModelTaskService, + clientModelTaskStatSvc interfaces.GrpcClientModelTaskStatService, + clientModelEnvironmentSvc interfaces.GrpcClientModelEnvironmentService, + c interfaces.GrpcClient, + ) { + svc.cfgSvc = cfgSvc + svc.modelSvc = modelSvc + svc.clientModelSvc = clientModelSvc + svc.clientModelNodeSvc = clientModelNodeSvc + svc.clientModelSpiderSvc = clientModelSpiderSvc + svc.clientModelTaskSvc = clientModelTaskSvc + svc.clientModelTaskStatSvc = clientModelTaskStatSvc + svc.clientModelEnvironmentSvc = clientModelEnvironmentSvc + svc.c = c + }); err != nil { + return nil, trace.TraceError(err) + } + + log.Debugf("[NewTaskHandlerService] svc[cfgPath: %s]", svc.cfgSvc.GetConfigPath()) + + return svc, nil +} + +var _service interfaces.TaskHandlerService + +func GetTaskHandlerService() (svr interfaces.TaskHandlerService, err error) { + if _service != nil { + return _service, nil + } + _service, err = NewTaskHandlerService() + if err != nil { + return nil, err + } + return _service, nil +} diff --git a/core/task/handler/service_v2.go b/core/task/handler/service_v2.go new file mode 100644 index 00000000..397d5f11 --- /dev/null +++ b/core/task/handler/service_v2.go @@ -0,0 +1,426 @@ +package handler + +import ( + "context" + "encoding/json" + "errors" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/constants" + errors2 "github.com/crawlab-team/crawlab/core/errors" + grpcclient "github.com/crawlab-team/crawlab/core/grpc/client" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/client" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + nodeconfig "github.com/crawlab-team/crawlab/core/node/config" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "sync" + "time" +) + +type ServiceV2 struct { + // dependencies + cfgSvc interfaces.NodeConfigService + c *grpcclient.GrpcClientV2 // grpc client + + // settings + //maxRunners int + exitWatchDuration time.Duration + reportInterval time.Duration + fetchInterval time.Duration + fetchTimeout time.Duration + cancelTimeout time.Duration + + // internals variables + stopped bool + mu sync.Mutex + runners sync.Map // pool of task runners started + syncLocks sync.Map // files sync locks map of task runners +} + +func (svc *ServiceV2) Start() { + // Initialize gRPC if not started + if !svc.c.IsStarted() { + svc.c.Start() + } + + go svc.ReportStatus() + go svc.Fetch() +} + +func (svc *ServiceV2) Run(taskId primitive.ObjectID) (err error) { + return svc.run(taskId) +} + +func (svc *ServiceV2) Reset() { + svc.mu.Lock() + defer svc.mu.Unlock() +} + +func (svc *ServiceV2) Cancel(taskId primitive.ObjectID) (err error) { + r, err := svc.getRunner(taskId) + if err != nil { + return err + } + if err := r.Cancel(); err != nil { + return err + } + return nil +} + +func (svc *ServiceV2) Fetch() { + for { + // wait + time.Sleep(svc.fetchInterval) + + // current node + n, err := svc.GetCurrentNode() + if err != nil { + continue + } + + // skip if node is not active or enabled + if !n.Active || !n.Enabled { + continue + } + + // validate if there are available runners + if svc.getRunnerCount() >= n.MaxRunners { + continue + } + + // stop + if svc.stopped { + return + } + + // fetch task + tid, err := svc.fetch() + if err != nil { + trace.PrintError(err) + continue + } + + // skip if no task id + if tid.IsZero() { + continue + } + + // run task + if err := svc.run(tid); err != nil { + trace.PrintError(err) + t, err := svc.GetTaskById(tid) + if err == nil && t.Status != constants.TaskStatusCancelled { + t.Error = err.Error() + t.Status = constants.TaskStatusError + t.SetUpdated(t.CreatedBy) + continue + } + continue + } + } +} + +func (svc *ServiceV2) ReportStatus() { + for { + if svc.stopped { + return + } + + // report handler status + if err := svc.reportStatus(); err != nil { + trace.PrintError(err) + } + + // wait + time.Sleep(svc.reportInterval) + } +} + +func (svc *ServiceV2) IsSyncLocked(path string) (ok bool) { + _, ok = svc.syncLocks.Load(path) + return ok +} + +func (svc *ServiceV2) LockSync(path string) { + svc.syncLocks.Store(path, true) +} + +func (svc *ServiceV2) UnlockSync(path string) { + svc.syncLocks.Delete(path) +} + +//func (svc *ServiceV2) GetMaxRunners() (maxRunners int) { +// return svc.maxRunners +//} +// +//func (svc *ServiceV2) SetMaxRunners(maxRunners int) { +// svc.maxRunners = maxRunners +//} + +func (svc *ServiceV2) GetExitWatchDuration() (duration time.Duration) { + return svc.exitWatchDuration +} + +func (svc *ServiceV2) SetExitWatchDuration(duration time.Duration) { + svc.exitWatchDuration = duration +} + +func (svc *ServiceV2) GetFetchInterval() (interval time.Duration) { + return svc.fetchInterval +} + +func (svc *ServiceV2) SetFetchInterval(interval time.Duration) { + svc.fetchInterval = interval +} + +func (svc *ServiceV2) GetReportInterval() (interval time.Duration) { + return svc.reportInterval +} + +func (svc *ServiceV2) SetReportInterval(interval time.Duration) { + svc.reportInterval = interval +} + +func (svc *ServiceV2) GetCancelTimeout() (timeout time.Duration) { + return svc.cancelTimeout +} + +func (svc *ServiceV2) SetCancelTimeout(timeout time.Duration) { + svc.cancelTimeout = timeout +} + +func (svc *ServiceV2) GetNodeConfigService() (cfgSvc interfaces.NodeConfigService) { + return svc.cfgSvc +} + +func (svc *ServiceV2) GetCurrentNode() (n *models.NodeV2, err error) { + // node key + nodeKey := svc.cfgSvc.GetNodeKey() + + // current node + if svc.cfgSvc.IsMaster() { + n, err = service.NewModelServiceV2[models.NodeV2]().GetOne(bson.M{"key": nodeKey}, nil) + } else { + n, err = client.NewModelServiceV2[models.NodeV2]().GetOne(bson.M{"key": nodeKey}, nil) + } + if err != nil { + return nil, err + } + + return n, nil +} + +func (svc *ServiceV2) GetTaskById(id primitive.ObjectID) (t *models.TaskV2, err error) { + if svc.cfgSvc.IsMaster() { + t, err = service.NewModelServiceV2[models.TaskV2]().GetById(id) + } else { + t, err = client.NewModelServiceV2[models.TaskV2]().GetById(id) + } + if err != nil { + return nil, err + } + + return t, nil +} + +func (svc *ServiceV2) GetSpiderById(id primitive.ObjectID) (s *models.SpiderV2, err error) { + if svc.cfgSvc.IsMaster() { + s, err = service.NewModelServiceV2[models.SpiderV2]().GetById(id) + } else { + s, err = client.NewModelServiceV2[models.SpiderV2]().GetById(id) + } + if err != nil { + return nil, err + } + + return s, nil +} + +func (svc *ServiceV2) getRunners() (runners []*Runner) { + svc.mu.Lock() + defer svc.mu.Unlock() + svc.runners.Range(func(key, value interface{}) bool { + r := value.(Runner) + runners = append(runners, &r) + return true + }) + return runners +} + +func (svc *ServiceV2) getRunnerCount() (count int) { + n, err := svc.GetCurrentNode() + if err != nil { + trace.PrintError(err) + return + } + query := bson.M{ + "node_id": n.Id, + "status": constants.TaskStatusRunning, + } + if svc.cfgSvc.IsMaster() { + count, err = service.NewModelServiceV2[models.TaskV2]().Count(query) + if err != nil { + trace.PrintError(err) + return + } + } else { + count, err = client.NewModelServiceV2[models.TaskV2]().Count(query) + if err != nil { + trace.PrintError(err) + return + } + } + return count +} + +func (svc *ServiceV2) getRunner(taskId primitive.ObjectID) (r interfaces.TaskRunner, err error) { + log.Debugf("[TaskHandlerService] getRunner: taskId[%v]", taskId) + v, ok := svc.runners.Load(taskId) + if !ok { + return nil, trace.TraceError(errors2.ErrorTaskNotExists) + } + switch v.(type) { + case interfaces.TaskRunner: + r = v.(interfaces.TaskRunner) + default: + return nil, trace.TraceError(errors2.ErrorModelInvalidType) + } + return r, nil +} + +func (svc *ServiceV2) addRunner(taskId primitive.ObjectID, r interfaces.TaskRunner) { + log.Debugf("[TaskHandlerService] addRunner: taskId[%v]", taskId) + svc.runners.Store(taskId, r) +} + +func (svc *ServiceV2) deleteRunner(taskId primitive.ObjectID) { + log.Debugf("[TaskHandlerService] deleteRunner: taskId[%v]", taskId) + svc.runners.Delete(taskId) +} + +func (svc *ServiceV2) reportStatus() (err error) { + // current node + n, err := svc.GetCurrentNode() + if err != nil { + return err + } + + // available runners of handler + ar := n.MaxRunners - svc.getRunnerCount() + + // set available runners + n.AvailableRunners = ar + + // save node + n.SetUpdated(n.CreatedBy) + if svc.cfgSvc.IsMaster() { + err = service.NewModelServiceV2[models.NodeV2]().ReplaceById(n.Id, *n) + } else { + err = client.NewModelServiceV2[models.NodeV2]().ReplaceById(n.Id, *n) + } + if err != nil { + return err + } + + return nil +} + +func (svc *ServiceV2) fetch() (tid primitive.ObjectID, err error) { + ctx, cancel := context.WithTimeout(context.Background(), svc.fetchTimeout) + defer cancel() + res, err := svc.c.TaskClient.Fetch(ctx, svc.c.NewRequest(nil)) + if err != nil { + return tid, trace.TraceError(err) + } + if err := json.Unmarshal(res.Data, &tid); err != nil { + return tid, trace.TraceError(err) + } + return tid, nil +} + +func (svc *ServiceV2) run(taskId primitive.ObjectID) (err error) { + // attempt to get runner from pool + _, ok := svc.runners.Load(taskId) + if ok { + return trace.TraceError(errors2.ErrorTaskAlreadyExists) + } + + // create a new task runner + r, err := NewTaskRunnerV2(taskId, svc) + if err != nil { + return trace.TraceError(err) + } + + // add runner to pool + svc.addRunner(taskId, r) + + // create a goroutine to run task + go func() { + // delete runner from pool + defer svc.deleteRunner(r.GetTaskId()) + defer func(r interfaces.TaskRunner) { + err := r.CleanUp() + if err != nil { + log.Errorf("task[%s] clean up error: %v", r.GetTaskId().Hex(), err) + } + }(r) + // run task process (blocking) + // error or finish after task runner ends + if err := r.Run(); err != nil { + switch { + case errors.Is(err, constants.ErrTaskError): + log.Errorf("task[%s] finished with error: %v", r.GetTaskId().Hex(), err) + case errors.Is(err, constants.ErrTaskCancelled): + log.Errorf("task[%s] cancelled", r.GetTaskId().Hex()) + default: + log.Errorf("task[%s] finished with unknown error: %v", r.GetTaskId().Hex(), err) + } + } + log.Infof("task[%s] finished", r.GetTaskId().Hex()) + }() + + return nil +} + +func NewTaskHandlerServiceV2() (svc2 *ServiceV2, err error) { + // service + svc := &ServiceV2{ + exitWatchDuration: 60 * time.Second, + fetchInterval: 1 * time.Second, + fetchTimeout: 15 * time.Second, + reportInterval: 5 * time.Second, + cancelTimeout: 5 * time.Second, + mu: sync.Mutex{}, + runners: sync.Map{}, + syncLocks: sync.Map{}, + } + + // dependency injection + svc.cfgSvc = nodeconfig.GetNodeConfigService() + + // grpc client + svc.c, err = grpcclient.NewGrpcClientV2() + if err != nil { + return nil, err + } + + log.Debugf("[NewTaskHandlerService] svc[cfgPath: %s]", svc.cfgSvc.GetConfigPath()) + + return svc, nil +} + +var _serviceV2 *ServiceV2 + +func GetTaskHandlerServiceV2() (svr *ServiceV2, err error) { + if _serviceV2 != nil { + return _serviceV2, nil + } + _serviceV2, err = NewTaskHandlerServiceV2() + if err != nil { + return nil, err + } + return _serviceV2, nil +} diff --git a/core/task/log/constants.go b/core/task/log/constants.go new file mode 100644 index 00000000..73676851 --- /dev/null +++ b/core/task/log/constants.go @@ -0,0 +1,12 @@ +package log + +const ( + MetadataName = "metadata.json" +) + +const ( + DriverTypeFile = "file" // raw file + DriverTypeFs = "fs" // file system (SeaweedFS) + DriverTypeMongo = "mongo" // mongodb + DriverTypeEs = "es" // elastic search +) diff --git a/core/task/log/default.go b/core/task/log/default.go new file mode 100644 index 00000000..2ac37f81 --- /dev/null +++ b/core/task/log/default.go @@ -0,0 +1,5 @@ +package log + +import "time" + +var DefaultLogTtl = 30 * 24 * time.Hour diff --git a/core/task/log/driver.go b/core/task/log/driver.go new file mode 100644 index 00000000..139ec44d --- /dev/null +++ b/core/task/log/driver.go @@ -0,0 +1,18 @@ +package log + +func GetLogDriver(logDriverType string) (driver Driver, err error) { + switch logDriverType { + case DriverTypeFile: + driver, err = GetFileLogDriver() + if err != nil { + return driver, err + } + case DriverTypeMongo: + return driver, ErrNotImplemented + case DriverTypeEs: + return driver, ErrNotImplemented + default: + return driver, ErrInvalidType + } + return driver, nil +} diff --git a/core/task/log/entity.go b/core/task/log/entity.go new file mode 100644 index 00000000..0583e41c --- /dev/null +++ b/core/task/log/entity.go @@ -0,0 +1,16 @@ +package log + +import "time" + +type Message struct { + Id int64 `json:"id" bson:"id"` + Msg string `json:"msg" bson:"msg"` + Ts time.Time `json:"ts" bson:"ts"` +} + +type Metadata struct { + Size int64 `json:"size,omitempty" bson:"size"` + TotalLines int64 `json:"total_lines,omitempty" bson:"total_lines"` + TotalBytes int64 `json:"total_bytes,omitempty" bson:"total_bytes"` + Md5 string `json:"md5,omitempty" bson:"md5"` +} diff --git a/core/task/log/errors.go b/core/task/log/errors.go new file mode 100644 index 00000000..eb7c1540 --- /dev/null +++ b/core/task/log/errors.go @@ -0,0 +1,8 @@ +package log + +import "errors" + +var ( + ErrInvalidType = errors.New("invalid type") + ErrNotImplemented = errors.New("not implemented") +) diff --git a/core/task/log/file_driver.go b/core/task/log/file_driver.go new file mode 100644 index 00000000..590a1c9a --- /dev/null +++ b/core/task/log/file_driver.go @@ -0,0 +1,284 @@ +package log + +import ( + "bufio" + "bytes" + "errors" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/spf13/viper" + "io" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" +) + +type FileLogDriver struct { + // settings + logFileName string + rootPath string + + // internals + mu sync.Mutex +} + +func (d *FileLogDriver) Init() (err error) { + go d.cleanup() + + return nil +} + +func (d *FileLogDriver) Close() (err error) { + return nil +} + +func (d *FileLogDriver) WriteLine(id string, line string) (err error) { + d.initDir(id) + + d.mu.Lock() + defer d.mu.Unlock() + filePath := d.getLogFilePath(id, d.logFileName) + + f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(0760)) + if err != nil { + return trace.TraceError(err) + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Errorf("close file error: %s", err.Error()) + } + }(f) + + _, err = f.WriteString(line + "\n") + if err != nil { + return trace.TraceError(err) + } + + return nil +} + +func (d *FileLogDriver) WriteLines(id string, lines []string) (err error) { + linesString := strings.Join(lines, "\n") + if err := d.WriteLine(id, linesString); err != nil { + return err + } + return nil +} + +func (d *FileLogDriver) Find(id string, pattern string, skip int, limit int) (lines []string, err error) { + if pattern != "" { + return lines, errors.New("not implemented") + } + if !utils.Exists(d.getLogFilePath(id, d.logFileName)) { + return nil, nil + } + + f, err := os.Open(d.getLogFilePath(id, d.logFileName)) + if err != nil { + return nil, trace.TraceError(err) + } + defer f.Close() + + sc := bufio.NewReaderSize(f, 1024*1024*10) + + i := -1 + for { + line, err := sc.ReadString(byte('\n')) + if err != nil { + break + } + line = strings.TrimSuffix(line, "\n") + + i++ + + if i < skip { + continue + } + + if i >= skip+limit { + break + } + + lines = append(lines, line) + } + + return lines, nil +} + +func (d *FileLogDriver) Count(id string, pattern string) (n int, err error) { + if pattern != "" { + return n, errors.New("not implemented") + } + if !utils.Exists(d.getLogFilePath(id, d.logFileName)) { + return 0, nil + } + + f, err := os.Open(d.getLogFilePath(id, d.logFileName)) + if err != nil { + return n, trace.TraceError(err) + } + return d.lineCounter(f) +} + +func (d *FileLogDriver) Flush() (err error) { + return nil +} + +func (d *FileLogDriver) getLogPath() (logPath string) { + return viper.GetString("log.path") +} + +func (d *FileLogDriver) getBasePath(id string) (filePath string) { + return filepath.Join(d.getLogPath(), id) +} + +func (d *FileLogDriver) getMetadataPath(id string) (filePath string) { + return filepath.Join(d.getBasePath(id), MetadataName) +} + +func (d *FileLogDriver) getLogFilePath(id, fileName string) (filePath string) { + return filepath.Join(d.getBasePath(id), fileName) +} + +func (d *FileLogDriver) getLogFiles(id string) (files []os.FileInfo) { + // 增加了对返回异常的捕获 + files, err := utils.ListDir(d.getBasePath(id)) + if err != nil { + trace.PrintError(err) + return nil + } + return +} + +func (d *FileLogDriver) initDir(id string) { + if !utils.Exists(d.getBasePath(id)) { + if err := os.MkdirAll(d.getBasePath(id), os.FileMode(0770)); err != nil { + trace.PrintError(err) + } + } +} + +func (d *FileLogDriver) lineCounter(r io.Reader) (n int, err error) { + buf := make([]byte, 32*1024) + count := 0 + lineSep := []byte{'\n'} + + for { + c, err := r.Read(buf) + count += bytes.Count(buf[:c], lineSep) + + switch { + case err == io.EOF: + return count, nil + + case err != nil: + return count, err + } + } +} + +func (d *FileLogDriver) getTtl() time.Duration { + ttl := viper.GetString("log.ttl") + if ttl == "" { + return DefaultLogTtl + } + + if strings.HasSuffix(ttl, "s") { + ttl = strings.TrimSuffix(ttl, "s") + n, err := strconv.Atoi(ttl) + if err != nil { + return DefaultLogTtl + } + return time.Duration(n) * time.Second + } else if strings.HasSuffix(ttl, "m") { + ttl = strings.TrimSuffix(ttl, "m") + n, err := strconv.Atoi(ttl) + if err != nil { + return DefaultLogTtl + } + return time.Duration(n) * time.Minute + } else if strings.HasSuffix(ttl, "h") { + ttl = strings.TrimSuffix(ttl, "h") + n, err := strconv.Atoi(ttl) + if err != nil { + return DefaultLogTtl + } + return time.Duration(n) * time.Hour + + } else if strings.HasSuffix(ttl, "d") { + ttl = strings.TrimSuffix(ttl, "d") + n, err := strconv.Atoi(ttl) + if err != nil { + return DefaultLogTtl + } + return time.Duration(n) * 24 * time.Hour + } else { + return DefaultLogTtl + } +} + +func (d *FileLogDriver) cleanup() { + if d.getLogPath() == "" { + return + } + if !utils.Exists(d.getLogPath()) { + if err := os.MkdirAll(d.getLogPath(), os.FileMode(0770)); err != nil { + log.Errorf("failed to create log directory: %s", d.getLogPath()) + trace.PrintError(err) + return + } + } + for { + // 增加对目录不存在的判断 + dirs, err := utils.ListDir(d.getLogPath()) + if err != nil { + trace.PrintError(err) + time.Sleep(10 * time.Minute) + continue + } + for _, dir := range dirs { + if time.Now().After(dir.ModTime().Add(d.getTtl())) { + if err := os.RemoveAll(d.getBasePath(dir.Name())); err != nil { + trace.PrintError(err) + continue + } + log.Infof("removed outdated log directory: %s", d.getBasePath(dir.Name())) + } + } + + time.Sleep(10 * time.Minute) + } +} + +var logDriver Driver + +func newFileLogDriver() (driver Driver, err error) { + // driver + driver = &FileLogDriver{ + logFileName: "log.txt", + mu: sync.Mutex{}, + } + + // init + if err := driver.Init(); err != nil { + return nil, err + } + + return driver, nil +} + +func GetFileLogDriver() (driver Driver, err error) { + if logDriver != nil { + return logDriver, nil + } + logDriver, err = newFileLogDriver() + if err != nil { + return nil, err + } + return logDriver, nil +} diff --git a/core/task/log/file_driver_test.go b/core/task/log/file_driver_test.go new file mode 100644 index 00000000..260edf71 --- /dev/null +++ b/core/task/log/file_driver_test.go @@ -0,0 +1,132 @@ +package log + +import ( + "fmt" + "github.com/stretchr/testify/require" + "go.mongodb.org/mongo-driver/bson/primitive" + "os" + "strings" + "testing" +) + +func setupFileDriverTest() { + cleanupFileDriverTest() + _ = os.MkdirAll("./tmp", os.ModePerm) +} + +func cleanupFileDriverTest() { + _ = os.RemoveAll("./tmp") +} + +func TestFileDriver_WriteLine(t *testing.T) { + setupFileDriverTest() + t.Cleanup(cleanupFileDriverTest) + + d, err := newFileLogDriver(nil) + require.Nil(t, err) + defer d.Close() + + id := primitive.NewObjectID() + + err = d.WriteLine(id.Hex(), "it works") + require.Nil(t, err) + + logFilePath := fmt.Sprintf("/var/log/crawlab/%s/log.txt", id.Hex()) + require.FileExists(t, logFilePath) + text, err := os.ReadFile(logFilePath) + require.Nil(t, err) + require.Equal(t, "it works\n", string(text)) +} + +func TestFileDriver_WriteLines(t *testing.T) { + setupFileDriverTest() + t.Cleanup(cleanupFileDriverTest) + + d, err := newFileLogDriver(nil) + require.Nil(t, err) + defer d.Close() + + id := primitive.NewObjectID() + + for i := 0; i < 100; i++ { + err = d.WriteLine(id.Hex(), "it works") + require.Nil(t, err) + } + + logFilePath := fmt.Sprintf("/var/log/crawlab/%s/log.txt", id.Hex()) + require.FileExists(t, logFilePath) + text, err := os.ReadFile(logFilePath) + require.Nil(t, err) + require.Contains(t, string(text), "it works\n") + lines := strings.Split(string(text), "\n") + require.Equal(t, 101, len(lines)) +} + +func TestFileDriver_Find(t *testing.T) { + setupFileDriverTest() + t.Cleanup(cleanupFileDriverTest) + + d, err := newFileLogDriver(nil) + require.Nil(t, err) + defer d.Close() + + id := primitive.NewObjectID() + + batch := 1000 + var lines []string + for i := 0; i < 10; i++ { + for j := 0; j < batch; j++ { + line := fmt.Sprintf("line: %d", i*batch+j+1) + lines = append(lines, line) + } + err = d.WriteLines(id.Hex(), lines) + require.Nil(t, err) + lines = []string{} + } + + driver := d + + lines, err = driver.Find(id.Hex(), "", 0, 10) + require.Nil(t, err) + require.Equal(t, 10, len(lines)) + require.Equal(t, "line: 1", lines[0]) + require.Equal(t, "line: 10", lines[len(lines)-1]) + + lines, err = driver.Find(id.Hex(), "", 0, 1) + require.Nil(t, err) + require.Equal(t, 1, len(lines)) + require.Equal(t, "line: 1", lines[0]) + require.Equal(t, "line: 1", lines[len(lines)-1]) + + lines, err = driver.Find(id.Hex(), "", 0, 1000) + require.Nil(t, err) + require.Equal(t, 1000, len(lines)) + require.Equal(t, "line: 1", lines[0]) + require.Equal(t, "line: 1000", lines[len(lines)-1]) + + lines, err = driver.Find(id.Hex(), "", 1000, 1000) + require.Nil(t, err) + require.Equal(t, 1000, len(lines)) + require.Equal(t, "line: 1001", lines[0]) + require.Equal(t, "line: 2000", lines[len(lines)-1]) + + lines, err = driver.Find(id.Hex(), "", 1001, 1000) + require.Nil(t, err) + require.Equal(t, 1000, len(lines)) + require.Equal(t, "line: 1002", lines[0]) + require.Equal(t, "line: 2001", lines[len(lines)-1]) + + lines, err = driver.Find(id.Hex(), "", 1001, 999) + require.Nil(t, err) + require.Equal(t, 999, len(lines)) + require.Equal(t, "line: 1002", lines[0]) + require.Equal(t, "line: 2000", lines[len(lines)-1]) + + lines, err = driver.Find(id.Hex(), "", 999, 2001) + require.Nil(t, err) + require.Equal(t, 2001, len(lines)) + require.Equal(t, "line: 1000", lines[0]) + require.Equal(t, "line: 3000", lines[len(lines)-1]) + + cleanupFileDriverTest() +} diff --git a/core/task/log/interface.go b/core/task/log/interface.go new file mode 100644 index 00000000..fd3745cb --- /dev/null +++ b/core/task/log/interface.go @@ -0,0 +1,10 @@ +package log + +type Driver interface { + Init() (err error) + Close() (err error) + WriteLine(id string, line string) (err error) + WriteLines(id string, lines []string) (err error) + Find(id string, pattern string, skip int, limit int) (lines []string, err error) + Count(id string, pattern string) (n int, err error) +} diff --git a/core/task/scheduler/options.go b/core/task/scheduler/options.go new file mode 100644 index 00000000..a6600fab --- /dev/null +++ b/core/task/scheduler/options.go @@ -0,0 +1,20 @@ +package scheduler + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "time" +) + +type Option func(svc interfaces.TaskSchedulerService) + +func WithConfigPath(path string) Option { + return func(svc interfaces.TaskSchedulerService) { + svc.SetConfigPath(path) + } +} + +func WithInterval(interval time.Duration) Option { + return func(svc interfaces.TaskSchedulerService) { + svc.SetInterval(interval) + } +} diff --git a/core/task/scheduler/service.go b/core/task/scheduler/service.go new file mode 100644 index 00000000..fb858545 --- /dev/null +++ b/core/task/scheduler/service.go @@ -0,0 +1,264 @@ +package scheduler + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/task" + grpc "github.com/crawlab-team/crawlab/grpc" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "time" +) + +type Service struct { + // dependencies + interfaces.TaskBaseService + nodeCfgSvc interfaces.NodeConfigService + modelSvc service.ModelService + svr interfaces.GrpcServer + handlerSvc interfaces.TaskHandlerService + + // settings + interval time.Duration +} + +func (svc *Service) Start() { + go svc.initTaskStatus() + go svc.cleanupTasks() + svc.Wait() + svc.Stop() +} + +func (svc *Service) Enqueue(t interfaces.Task) (t2 interfaces.Task, err error) { + // set task status + t.SetStatus(constants.TaskStatusPending) + + // user + var u *models.User + if !t.GetUserId().IsZero() { + u, _ = svc.modelSvc.GetUserById(t.GetUserId()) + } + + // add task + if err = delegate.NewModelDelegate(t, u).Add(); err != nil { + return nil, err + } + + // task queue item + tq := &models.TaskQueueItem{ + Id: t.GetId(), + Priority: t.GetPriority(), + NodeId: t.GetNodeId(), + } + + // task stat + ts := &models.TaskStat{ + Id: t.GetId(), + CreateTs: time.Now(), + } + + // enqueue task + _, err = mongo.GetMongoCol(interfaces.ModelColNameTaskQueue).Insert(tq) + if err != nil { + return nil, trace.TraceError(err) + } + + // add task stat + _, err = mongo.GetMongoCol(interfaces.ModelColNameTaskStat).Insert(ts) + if err != nil { + return nil, trace.TraceError(err) + } + + // success + return t, nil +} + +func (svc *Service) Cancel(id primitive.ObjectID, args ...interface{}) (err error) { + // task + t, err := svc.modelSvc.GetTaskById(id) + if err != nil { + return trace.TraceError(err) + } + + // initial status + initialStatus := t.Status + + // set task status as "cancelled" + _ = svc.SaveTask(t, constants.TaskStatusCancelled) + + // set status of pending tasks as "cancelled" and remove from task item queue + if initialStatus == constants.TaskStatusPending { + // remove from task item queue + if err := mongo.GetMongoCol(interfaces.ModelColNameTaskQueue).DeleteId(t.GetId()); err != nil { + return trace.TraceError(err) + } + return nil + } + + // whether task is running on master node + isMasterTask, err := svc.isMasterNode(t) + if err != nil { + // when error, force status being set as "cancelled" + return svc.SaveTask(t, constants.TaskStatusCancelled) + } + + // node + n, err := svc.modelSvc.GetNodeById(t.GetNodeId()) + if err != nil { + return trace.TraceError(err) + } + + if isMasterTask { + // cancel task on master + if err := svc.handlerSvc.Cancel(id); err != nil { + return trace.TraceError(err) + } + // cancel success + return nil + } else { + // send to cancel task on worker nodes + if err := svc.svr.SendStreamMessageWithData("node:"+n.GetKey(), grpc.StreamMessageCode_CANCEL_TASK, t); err != nil { + return trace.TraceError(err) + } + // cancel success + return nil + } +} + +func (svc *Service) SetInterval(interval time.Duration) { + svc.interval = interval +} + +// initTaskStatus initialize task status of existing tasks +func (svc *Service) initTaskStatus() { + // set status of running tasks as TaskStatusAbnormal + runningTasks, err := svc.modelSvc.GetTaskList(bson.M{ + "status": bson.M{ + "$in": []string{ + constants.TaskStatusPending, + constants.TaskStatusRunning, + }, + }, + }, nil) + if err != nil { + if err == mongo2.ErrNoDocuments { + return + } + trace.PrintError(err) + } + for _, t := range runningTasks { + go func(t *models.Task) { + if err := svc.SaveTask(t, constants.TaskStatusAbnormal); err != nil { + trace.PrintError(err) + } + }(&t) + } + if err := svc.modelSvc.GetBaseService(interfaces.ModelIdTaskQueue).DeleteList(nil); err != nil { + return + } +} + +func (svc *Service) isMasterNode(t *models.Task) (ok bool, err error) { + if t.GetNodeId().IsZero() { + return false, trace.TraceError(errors.ErrorTaskNoNodeId) + } + n, err := svc.modelSvc.GetNodeById(t.GetNodeId()) + if err != nil { + if err == mongo2.ErrNoDocuments { + return false, trace.TraceError(errors.ErrorTaskNodeNotFound) + } + return false, trace.TraceError(err) + } + return n.IsMaster, nil +} + +func (svc *Service) cleanupTasks() { + for { + // task stats over 30 days ago + taskStats, err := svc.modelSvc.GetTaskStatList(bson.M{ + "create_ts": bson.M{ + "$lt": time.Now().Add(-30 * 24 * time.Hour), + }, + }, nil) + if err != nil { + time.Sleep(30 * time.Minute) + continue + } + + // task ids + var ids []primitive.ObjectID + for _, ts := range taskStats { + ids = append(ids, ts.Id) + } + + if len(ids) > 0 { + // remove tasks + if err := svc.modelSvc.GetBaseService(interfaces.ModelIdTask).DeleteList(bson.M{ + "_id": bson.M{"$in": ids}, + }); err != nil { + trace.PrintError(err) + } + + // remove task stats + if err := svc.modelSvc.GetBaseService(interfaces.ModelIdTaskStat).DeleteList(bson.M{ + "_id": bson.M{"$in": ids}, + }); err != nil { + trace.PrintError(err) + } + } + + time.Sleep(30 * time.Minute) + } +} + +func NewTaskSchedulerService() (svc2 interfaces.TaskSchedulerService, err error) { + // base service + baseSvc, err := task.NewBaseService() + if err != nil { + return nil, trace.TraceError(err) + } + + // service + svc := &Service{ + TaskBaseService: baseSvc, + interval: 5 * time.Second, + } + + // dependency injection + if err := container.GetContainer().Invoke(func( + nodeCfgSvc interfaces.NodeConfigService, + modelSvc service.ModelService, + svr interfaces.GrpcServer, + handlerSvc interfaces.TaskHandlerService, + ) { + svc.nodeCfgSvc = nodeCfgSvc + svc.modelSvc = modelSvc + svc.svr = svr + svc.handlerSvc = handlerSvc + }); err != nil { + return nil, err + } + + return svc, nil +} + +var svc interfaces.TaskSchedulerService + +func GetTaskSchedulerService() (svr interfaces.TaskSchedulerService, err error) { + if svc != nil { + return svc, nil + } + svc, err = NewTaskSchedulerService() + if err != nil { + return nil, err + } + return svc, nil +} diff --git a/core/task/scheduler/service_v2.go b/core/task/scheduler/service_v2.go new file mode 100644 index 00000000..b5b6cfa4 --- /dev/null +++ b/core/task/scheduler/service_v2.go @@ -0,0 +1,264 @@ +package scheduler + +import ( + errors2 "errors" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + grpc "github.com/crawlab-team/crawlab/grpc" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "time" +) + +type ServiceV2 struct { + // dependencies + nodeCfgSvc interfaces.NodeConfigService + svr interfaces.GrpcServer + handlerSvc interfaces.TaskHandlerService + + // settings + interval time.Duration +} + +func (svc *ServiceV2) Start() { + go svc.initTaskStatus() + go svc.cleanupTasks() + utils.DefaultWait() +} + +func (svc *ServiceV2) Enqueue(t *models.TaskV2, by primitive.ObjectID) (t2 *models.TaskV2, err error) { + // set task status + t.Status = constants.TaskStatusPending + t.SetCreatedBy(by) + t.SetUpdated(by) + + // add task + taskModelSvc := service.NewModelServiceV2[models.TaskV2]() + _, err = taskModelSvc.InsertOne(*t) + if err != nil { + return nil, err + } + + // task queue item + tq := models.TaskQueueItemV2{ + Priority: t.Priority, + NodeId: t.NodeId, + } + tq.SetId(t.Id) + + // task stat + ts := models.TaskStatV2{ + CreateTs: time.Now(), + } + ts.SetId(t.Id) + + // enqueue task + _, err = service.NewModelServiceV2[models.TaskQueueItemV2]().InsertOne(tq) + if err != nil { + return nil, trace.TraceError(err) + } + + // add task stat + _, err = service.NewModelServiceV2[models.TaskStatV2]().InsertOne(ts) + if err != nil { + return nil, trace.TraceError(err) + } + + // success + return t, nil +} + +func (svc *ServiceV2) Cancel(id primitive.ObjectID, by primitive.ObjectID) (err error) { + // task + t, err := service.NewModelServiceV2[models.TaskV2]().GetById(id) + if err != nil { + return trace.TraceError(err) + } + + // initial status + initialStatus := t.Status + + // set task status as "cancelled" + t.Status = constants.TaskStatusCancelled + _ = svc.SaveTask(t, by) + + // set status of pending tasks as "cancelled" and remove from task item queue + if initialStatus == constants.TaskStatusPending { + // remove from task item queue + if err := service.NewModelServiceV2[models.TaskQueueItemV2]().DeleteById(t.Id); err != nil { + return trace.TraceError(err) + } + return nil + } + + // whether task is running on master node + isMasterTask, err := svc.isMasterNode(t) + if err != nil { + // when error, force status being set as "cancelled" + t.Status = constants.TaskStatusCancelled + return svc.SaveTask(t, by) + } + + // node + n, err := service.NewModelServiceV2[models.NodeV2]().GetById(t.NodeId) + if err != nil { + return trace.TraceError(err) + } + + if isMasterTask { + // cancel task on master + if err := svc.handlerSvc.Cancel(id); err != nil { + return trace.TraceError(err) + } + // cancel success + return nil + } else { + // send to cancel task on worker nodes + if err := svc.svr.SendStreamMessageWithData("node:"+n.Key, grpc.StreamMessageCode_CANCEL_TASK, t); err != nil { + return trace.TraceError(err) + } + // cancel success + return nil + } +} + +func (svc *ServiceV2) SetInterval(interval time.Duration) { + svc.interval = interval +} + +func (svc *ServiceV2) SaveTask(t *models.TaskV2, by primitive.ObjectID) (err error) { + if t.Id.IsZero() { + t.SetCreated(by) + t.SetUpdated(by) + _, err = service.NewModelServiceV2[models.TaskV2]().InsertOne(*t) + return err + } else { + t.SetUpdated(by) + return service.NewModelServiceV2[models.TaskV2]().ReplaceById(t.Id, *t) + } +} + +// initTaskStatus initialize task status of existing tasks +func (svc *ServiceV2) initTaskStatus() { + // set status of running tasks as TaskStatusAbnormal + runningTasks, err := service.NewModelServiceV2[models.TaskV2]().GetMany(bson.M{ + "status": bson.M{ + "$in": []string{ + constants.TaskStatusPending, + constants.TaskStatusRunning, + }, + }, + }, nil) + if err != nil { + if errors2.Is(err, mongo2.ErrNoDocuments) { + return + } + trace.PrintError(err) + } + for _, t := range runningTasks { + go func(t *models.TaskV2) { + t.Status = constants.TaskStatusAbnormal + if err := svc.SaveTask(t, primitive.NilObjectID); err != nil { + trace.PrintError(err) + } + }(&t) + } + if err := service.NewModelServiceV2[models.TaskQueueItemV2]().DeleteMany(nil); err != nil { + return + } +} + +func (svc *ServiceV2) isMasterNode(t *models.TaskV2) (ok bool, err error) { + if t.NodeId.IsZero() { + return false, trace.TraceError(errors.ErrorTaskNoNodeId) + } + n, err := service.NewModelServiceV2[models.NodeV2]().GetById(t.NodeId) + if err != nil { + if errors2.Is(err, mongo2.ErrNoDocuments) { + return false, trace.TraceError(errors.ErrorTaskNodeNotFound) + } + return false, trace.TraceError(err) + } + return n.IsMaster, nil +} + +func (svc *ServiceV2) cleanupTasks() { + for { + // task stats over 30 days ago + taskStats, err := service.NewModelServiceV2[models.TaskStatV2]().GetMany(bson.M{ + "create_ts": bson.M{ + "$lt": time.Now().Add(-30 * 24 * time.Hour), + }, + }, nil) + if err != nil { + time.Sleep(30 * time.Minute) + continue + } + + // task ids + var ids []primitive.ObjectID + for _, ts := range taskStats { + ids = append(ids, ts.Id) + } + + if len(ids) > 0 { + // remove tasks + if err := service.NewModelServiceV2[models.TaskV2]().DeleteMany(bson.M{ + "_id": bson.M{"$in": ids}, + }); err != nil { + trace.PrintError(err) + } + + // remove task stats + if err := service.NewModelServiceV2[models.TaskStatV2]().DeleteMany(bson.M{ + "_id": bson.M{"$in": ids}, + }); err != nil { + trace.PrintError(err) + } + } + + time.Sleep(30 * time.Minute) + } +} + +func NewTaskSchedulerServiceV2() (svc2 *ServiceV2, err error) { + // service + svc := &ServiceV2{ + interval: 5 * time.Second, + } + + // dependency injection + if err := container.GetContainer().Invoke(func( + nodeCfgSvc interfaces.NodeConfigService, + svr interfaces.GrpcServer, + handlerSvc interfaces.TaskHandlerService, + ) { + svc.nodeCfgSvc = nodeCfgSvc + svc.svr = svr + svc.handlerSvc = handlerSvc + }); err != nil { + return nil, err + } + + return svc, nil +} + +var svcV2 *ServiceV2 + +func GetTaskSchedulerServiceV2() (svr *ServiceV2, err error) { + if svcV2 != nil { + return svcV2, nil + } + svcV2, err = NewTaskSchedulerServiceV2() + if err != nil { + return nil, err + } + return svcV2, nil +} diff --git a/core/task/stats/options.go b/core/task/stats/options.go new file mode 100644 index 00000000..37e7a823 --- /dev/null +++ b/core/task/stats/options.go @@ -0,0 +1,11 @@ +package stats + +import "github.com/crawlab-team/crawlab/core/interfaces" + +type Option func(service interfaces.TaskStatsService) + +func WithConfigPath(path string) Option { + return func(svc interfaces.TaskStatsService) { + svc.SetConfigPath(path) + } +} diff --git a/core/task/stats/service.go b/core/task/stats/service.go new file mode 100644 index 00000000..ee12d762 --- /dev/null +++ b/core/task/stats/service.go @@ -0,0 +1,155 @@ +package stats + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/result" + "github.com/crawlab-team/crawlab/core/task" + "github.com/crawlab-team/crawlab/core/task/log" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "sync" + "time" +) + +type Service struct { + // dependencies + interfaces.TaskBaseService + nodeCfgSvc interfaces.NodeConfigService + modelSvc service.ModelService + + // internals + mu sync.Mutex + resultServices sync.Map + rsTtl time.Duration + logDriver log.Driver +} + +func (svc *Service) Init() (err error) { + go svc.cleanup() + return nil +} + +func (svc *Service) InsertData(id primitive.ObjectID, records ...interface{}) (err error) { + resultSvc, err := svc.getResultService(id) + if err != nil { + return err + } + if err := resultSvc.Insert(records...); err != nil { + return err + } + go svc.updateTaskStats(id, len(records)) + return nil +} + +func (svc *Service) InsertLogs(id primitive.ObjectID, logs ...string) (err error) { + return svc.logDriver.WriteLines(id.Hex(), logs) +} + +func (svc *Service) getResultService(id primitive.ObjectID) (resultSvc interfaces.ResultService, err error) { + // atomic operation + svc.mu.Lock() + defer svc.mu.Unlock() + + // attempt to get from cache + res, _ := svc.resultServices.Load(id.Hex()) + if res != nil { + // hit in cache + resultSvc, ok := res.(interfaces.ResultService) + resultSvc.SetTime(time.Now()) + if ok { + return resultSvc, nil + } + } + + // task + t, err := svc.modelSvc.GetTaskById(id) + if err != nil { + return nil, err + } + + // result service + resultSvc, err = result.GetResultService(t.SpiderId) + if err != nil { + return nil, err + } + + // store in cache + svc.resultServices.Store(id.Hex(), resultSvc) + + return resultSvc, nil +} + +func (svc *Service) updateTaskStats(id primitive.ObjectID, resultCount int) { + _ = mongo.GetMongoCol(interfaces.ModelColNameTaskStat).UpdateId(id, bson.M{ + "$inc": bson.M{ + "result_count": resultCount, + }, + }) +} + +func (svc *Service) cleanup() { + for { + // atomic operation + svc.mu.Lock() + + svc.resultServices.Range(func(key, value interface{}) bool { + rs := value.(interfaces.ResultService) + if time.Now().After(rs.GetTime().Add(svc.rsTtl)) { + svc.resultServices.Delete(key) + } + return true + }) + + svc.mu.Unlock() + + time.Sleep(10 * time.Minute) + } +} + +func NewTaskStatsService() (svc2 interfaces.TaskStatsService, err error) { + // base service + baseSvc, err := task.NewBaseService() + if err != nil { + return nil, trace.TraceError(err) + } + + // service + svc := &Service{ + mu: sync.Mutex{}, + TaskBaseService: baseSvc, + resultServices: sync.Map{}, + } + + // dependency injection + if err := container.GetContainer().Invoke(func(nodeCfgSvc interfaces.NodeConfigService, modelSvc service.ModelService) { + svc.nodeCfgSvc = nodeCfgSvc + svc.modelSvc = modelSvc + }); err != nil { + return nil, trace.TraceError(err) + } + + // log driver + svc.logDriver, err = log.GetLogDriver(log.DriverTypeFile) + if err != nil { + return nil, err + } + + return svc, nil +} + +var _service interfaces.TaskStatsService + +func GetTaskStatsService() (svr interfaces.TaskStatsService, err error) { + if _service != nil { + return _service, nil + } + _service, err = NewTaskStatsService() + if err != nil { + return nil, err + } + return _service, nil +} diff --git a/core/task/stats/service_v2.go b/core/task/stats/service_v2.go new file mode 100644 index 00000000..b198c95d --- /dev/null +++ b/core/task/stats/service_v2.go @@ -0,0 +1,143 @@ +package stats + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + nodeconfig "github.com/crawlab-team/crawlab/core/node/config" + "github.com/crawlab-team/crawlab/core/result" + "github.com/crawlab-team/crawlab/core/task/log" + "github.com/crawlab-team/go-trace" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "sync" + "time" +) + +type ServiceV2 struct { + // dependencies + nodeCfgSvc interfaces.NodeConfigService + modelSvc service.ModelService + + // internals + mu sync.Mutex + resultServices sync.Map + rsTtl time.Duration + logDriver log.Driver +} + +func (svc *ServiceV2) Init() (err error) { + go svc.cleanup() + return nil +} + +func (svc *ServiceV2) InsertData(id primitive.ObjectID, records ...interface{}) (err error) { + resultSvc, err := svc.getResultService(id) + if err != nil { + return err + } + if err := resultSvc.Insert(records...); err != nil { + return err + } + go svc.updateTaskStats(id, len(records)) + return nil +} + +func (svc *ServiceV2) InsertLogs(id primitive.ObjectID, logs ...string) (err error) { + return svc.logDriver.WriteLines(id.Hex(), logs) +} + +func (svc *ServiceV2) getResultService(id primitive.ObjectID) (resultSvc interfaces.ResultService, err error) { + // atomic operation + svc.mu.Lock() + defer svc.mu.Unlock() + + // attempt to get from cache + res, _ := svc.resultServices.Load(id.Hex()) + if res != nil { + // hit in cache + resultSvc, ok := res.(interfaces.ResultService) + resultSvc.SetTime(time.Now()) + if ok { + return resultSvc, nil + } + } + + // task + t, err := svc.modelSvc.GetTaskById(id) + if err != nil { + return nil, err + } + + // result service + resultSvc, err = result.GetResultService(t.SpiderId) + if err != nil { + return nil, err + } + + // store in cache + svc.resultServices.Store(id.Hex(), resultSvc) + + return resultSvc, nil +} + +func (svc *ServiceV2) updateTaskStats(id primitive.ObjectID, resultCount int) { + err := service.NewModelServiceV2[models.TaskStatV2]().UpdateById(id, bson.M{ + "$inc": bson.M{ + "result_count": resultCount, + }, + }) + if err != nil { + trace.PrintError(err) + } +} + +func (svc *ServiceV2) cleanup() { + for { + // atomic operation + svc.mu.Lock() + + svc.resultServices.Range(func(key, value interface{}) bool { + rs := value.(interfaces.ResultService) + if time.Now().After(rs.GetTime().Add(svc.rsTtl)) { + svc.resultServices.Delete(key) + } + return true + }) + + svc.mu.Unlock() + + time.Sleep(10 * time.Minute) + } +} + +func NewTaskStatsServiceV2() (svc2 *ServiceV2, err error) { + // service + svc := &ServiceV2{ + mu: sync.Mutex{}, + resultServices: sync.Map{}, + } + + svc.nodeCfgSvc = nodeconfig.GetNodeConfigService() + + // log driver + svc.logDriver, err = log.GetLogDriver(log.DriverTypeFile) + if err != nil { + return nil, err + } + + return svc, nil +} + +var _serviceV2 *ServiceV2 + +func GetTaskStatsServiceV2() (svr *ServiceV2, err error) { + if _serviceV2 != nil { + return _serviceV2, nil + } + _serviceV2, err = NewTaskStatsServiceV2() + if err != nil { + return nil, err + } + return _serviceV2, nil +} diff --git a/core/user/options.go b/core/user/options.go new file mode 100644 index 00000000..81c208d7 --- /dev/null +++ b/core/user/options.go @@ -0,0 +1,11 @@ +package user + +import "github.com/crawlab-team/crawlab/core/interfaces" + +type Option func(svc interfaces.UserService) + +func WithJwtSecret(secret string) Option { + return func(svc interfaces.UserService) { + svc.SetJwtSecret(secret) + } +} diff --git a/core/user/service.go b/core/user/service.go new file mode 100644 index 00000000..97a2ad9d --- /dev/null +++ b/core/user/service.go @@ -0,0 +1,238 @@ +package user + +import ( + mongo2 "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/container" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/delegate" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "time" +) + +type Service struct { + // settings variables + jwtSecret string + jwtSigningMethod jwt.SigningMethod + + // dependencies + modelSvc service.ModelService +} + +func (svc *Service) Init() (err error) { + _, err = svc.modelSvc.GetUserByUsername(constants.DefaultAdminUsername, nil) + if err == nil { + return nil + } + if err.Error() != mongo.ErrNoDocuments.Error() { + return err + } + return svc.Create(&interfaces.UserCreateOptions{ + Username: constants.DefaultAdminUsername, + Password: constants.DefaultAdminPassword, + Role: constants.RoleAdmin, + }) +} + +func (svc *Service) SetJwtSecret(secret string) { + svc.jwtSecret = secret +} + +func (svc *Service) SetJwtSigningMethod(method jwt.SigningMethod) { + svc.jwtSigningMethod = method +} + +func (svc *Service) Create(opts *interfaces.UserCreateOptions, args ...interface{}) (err error) { + actor := utils.GetUserFromArgs(args...) + + // validate options + if opts.Username == "" || opts.Password == "" { + return trace.TraceError(errors.ErrorUserMissingRequiredFields) + } + if len(opts.Password) < 5 { + return trace.TraceError(errors.ErrorUserInvalidPassword) + } + + // normalize options + if opts.Role == "" { + opts.Role = constants.RoleNormal + } + + // check if user exists + if u, err := svc.modelSvc.GetUserByUsername(opts.Username, nil); err == nil && u != nil && !u.Id.IsZero() { + return trace.TraceError(errors.ErrorUserAlreadyExists) + } + + // transaction + return mongo2.RunTransaction(func(ctx mongo.SessionContext) error { + // add user + u := &models.User{ + Username: opts.Username, + Role: opts.Role, + Email: opts.Email, + } + if err := delegate.NewModelDelegate(u, actor).Add(); err != nil { + return err + } + + // add password + p := &models.Password{ + Id: u.Id, + Password: utils.EncryptMd5(opts.Password), + } + if err := delegate.NewModelDelegate(p, actor).Add(); err != nil { + return err + } + + return nil + }) +} + +func (svc *Service) Login(opts *interfaces.UserLoginOptions) (token string, u interfaces.User, err error) { + u, err = svc.modelSvc.GetUserByUsername(opts.Username, nil) + if err != nil { + return "", nil, err + } + p, err := svc.modelSvc.GetPasswordById(u.GetId()) + if err != nil { + return "", nil, err + } + if p.Password != utils.EncryptMd5(opts.Password) { + return "", nil, errors.ErrorUserMismatch + } + token, err = svc.makeToken(u) + if err != nil { + return "", nil, err + } + return token, u, nil +} + +func (svc *Service) CheckToken(tokenStr string) (u interfaces.User, err error) { + return svc.checkToken(tokenStr) +} + +func (svc *Service) ChangePassword(id primitive.ObjectID, password string, args ...interface{}) (err error) { + actor := utils.GetUserFromArgs(args...) + + p, err := svc.modelSvc.GetPasswordById(id) + if err != nil { + return err + } + p.Password = utils.EncryptMd5(password) + if err := delegate.NewModelDelegate(p, actor).Save(); err != nil { + return err + } + return nil +} + +func (svc *Service) MakeToken(user interfaces.User) (tokenStr string, err error) { + return svc.makeToken(user) +} + +func (svc *Service) GetCurrentUser(c *gin.Context) (user interfaces.User, err error) { + // token string + tokenStr := c.GetHeader("Authorization") + + // user + u, err := userSvc.CheckToken(tokenStr) + if err != nil { + return nil, err + } + + return u, nil +} + +func (svc *Service) makeToken(user interfaces.User) (tokenStr string, err error) { + token := jwt.NewWithClaims(svc.jwtSigningMethod, jwt.MapClaims{ + "id": user.GetId(), + "username": user.GetUsername(), + "nbf": time.Now().Unix(), + }) + return token.SignedString([]byte(svc.jwtSecret)) +} + +func (svc *Service) checkToken(tokenStr string) (user interfaces.User, err error) { + token, err := jwt.Parse(tokenStr, svc.getSecretFunc()) + if err != nil { + return + } + + claim, ok := token.Claims.(jwt.MapClaims) + if !ok { + err = errors.ErrorUserInvalidType + return + } + + if !token.Valid { + err = errors.ErrorUserInvalidToken + return + } + + id, err := primitive.ObjectIDFromHex(claim["id"].(string)) + if err != nil { + return user, err + } + username := claim["username"].(string) + user, err = svc.modelSvc.GetUserById(id) + if err != nil { + err = errors.ErrorUserNotExists + return + } + + if username != user.GetUsername() { + err = errors.ErrorUserMismatch + return + } + + return +} + +func (svc *Service) getSecretFunc() jwt.Keyfunc { + return func(token *jwt.Token) (interface{}, error) { + return []byte(svc.jwtSecret), nil + } +} + +func NewUserService() (svc2 interfaces.UserService, err error) { + // service + svc := &Service{ + jwtSecret: "crawlab", + jwtSigningMethod: jwt.SigningMethodHS256, + } + + // dependency injection + if err := container.GetContainer().Invoke(func(modelSvc service.ModelService) { + svc.modelSvc = modelSvc + }); err != nil { + return nil, trace.TraceError(err) + } + + // initialize + if err := svc.Init(); err != nil { + return nil, trace.TraceError(err) + } + + return svc, nil +} + +var userSvc interfaces.UserService + +func GetUserService() (svc interfaces.UserService, err error) { + if userSvc != nil { + return userSvc, nil + } + svc, err = NewUserService() + if err != nil { + return nil, err + } + userSvc = svc + return svc, nil +} diff --git a/core/user/service_v2.go b/core/user/service_v2.go new file mode 100644 index 00000000..e4aa72b6 --- /dev/null +++ b/core/user/service_v2.go @@ -0,0 +1,212 @@ +package user + +import ( + mongo2 "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "time" +) + +type ServiceV2 struct { + jwtSecret string + jwtSigningMethod jwt.SigningMethod + modelSvc *service.ModelServiceV2[models.UserV2] +} + +func (svc *ServiceV2) Init() (err error) { + _, err = svc.modelSvc.GetOne(bson.M{"username": constants.DefaultAdminUsername}, nil) + if err == nil { + return nil + } + if err.Error() != mongo.ErrNoDocuments.Error() { + return err + } + return svc.Create( + constants.DefaultAdminUsername, + constants.DefaultAdminPassword, + constants.RoleAdmin, + "", + primitive.NilObjectID, + ) +} + +func (svc *ServiceV2) SetJwtSecret(secret string) { + svc.jwtSecret = secret +} + +func (svc *ServiceV2) SetJwtSigningMethod(method jwt.SigningMethod) { + svc.jwtSigningMethod = method +} + +func (svc *ServiceV2) Create(username, password, role, email string, by primitive.ObjectID) (err error) { + // validate options + if username == "" || password == "" { + return trace.TraceError(errors.ErrorUserMissingRequiredFields) + } + if len(password) < 5 { + return trace.TraceError(errors.ErrorUserInvalidPassword) + } + + // normalize options + if role == "" { + role = constants.RoleNormal + } + + // check if user exists + if u, err := svc.modelSvc.GetOne(bson.M{"username": username}, nil); err == nil && u != nil && !u.Id.IsZero() { + return trace.TraceError(errors.ErrorUserAlreadyExists) + } + + // transaction + return mongo2.RunTransaction(func(ctx mongo.SessionContext) error { + // add user + u := models.UserV2{ + Username: username, + Role: role, + Password: utils.EncryptMd5(password), + Email: email, + } + u.SetCreated(by) + u.SetUpdated(by) + _, err = svc.modelSvc.InsertOne(u) + + return err + }) +} + +func (svc *ServiceV2) Login(username, password string) (token string, u *models.UserV2, err error) { + u, err = svc.modelSvc.GetOne(bson.M{"username": username}, nil) + if err != nil { + return "", nil, err + } + if u.Password != utils.EncryptMd5(password) { + return "", nil, errors.ErrorUserMismatch + } + token, err = svc.makeToken(u) + if err != nil { + return "", nil, err + } + return token, u, nil +} + +func (svc *ServiceV2) CheckToken(tokenStr string) (u *models.UserV2, err error) { + return svc.checkToken(tokenStr) +} + +func (svc *ServiceV2) ChangePassword(id primitive.ObjectID, password string, by primitive.ObjectID) (err error) { + u, err := svc.modelSvc.GetById(id) + if err != nil { + return err + } + u.Password = utils.EncryptMd5(password) + u.SetCreatedBy(by) + return svc.modelSvc.ReplaceById(id, *u) +} + +func (svc *ServiceV2) MakeToken(user *models.UserV2) (tokenStr string, err error) { + return svc.makeToken(user) +} + +func (svc *ServiceV2) GetCurrentUser(c *gin.Context) (user interfaces.User, err error) { + // token string + tokenStr := c.GetHeader("Authorization") + + // user + u, err := userSvc.CheckToken(tokenStr) + if err != nil { + return nil, err + } + + return u, nil +} + +func (svc *ServiceV2) makeToken(user *models.UserV2) (tokenStr string, err error) { + token := jwt.NewWithClaims(svc.jwtSigningMethod, jwt.MapClaims{ + "id": user.Id, + "username": user.Username, + "nbf": time.Now().Unix(), + }) + return token.SignedString([]byte(svc.jwtSecret)) +} + +func (svc *ServiceV2) checkToken(tokenStr string) (user *models.UserV2, err error) { + token, err := jwt.Parse(tokenStr, svc.getSecretFunc()) + if err != nil { + return + } + + claim, ok := token.Claims.(jwt.MapClaims) + if !ok { + err = errors.ErrorUserInvalidType + return + } + + if !token.Valid { + err = errors.ErrorUserInvalidToken + return + } + + id, err := primitive.ObjectIDFromHex(claim["id"].(string)) + if err != nil { + return user, err + } + username := claim["username"].(string) + user, err = svc.modelSvc.GetById(id) + if err != nil { + err = errors.ErrorUserNotExists + return + } + + if username != user.Username { + err = errors.ErrorUserMismatch + return + } + + return +} + +func (svc *ServiceV2) getSecretFunc() jwt.Keyfunc { + return func(token *jwt.Token) (interface{}, error) { + return []byte(svc.jwtSecret), nil + } +} + +func NewUserServiceV2() (svc *ServiceV2, err error) { + // service + svc = &ServiceV2{ + modelSvc: service.NewModelServiceV2[models.UserV2](), + jwtSecret: "crawlab", + jwtSigningMethod: jwt.SigningMethodHS256, + } + + // initialize + if err := svc.Init(); err != nil { + return nil, trace.TraceError(err) + } + + return svc, nil +} + +var userSvcV2 *ServiceV2 + +func GetUserServiceV2() (svc *ServiceV2, err error) { + if userSvcV2 != nil { + return userSvcV2, nil + } + svc, err = NewUserServiceV2() + if err != nil { + return nil, err + } + userSvcV2 = svc + return svc, nil +} diff --git a/core/user/test/base.go b/core/user/test/base.go new file mode 100644 index 00000000..392a7e06 --- /dev/null +++ b/core/user/test/base.go @@ -0,0 +1,65 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/service" + "github.com/crawlab-team/crawlab/core/user" + "go.uber.org/dig" + "testing" +) + +func init() { + var err error + T, err = NewTest() + if err != nil { + panic(err) + } +} + +var T *Test + +type Test struct { + // dependencies + modelSvc service.ModelService + userSvc interfaces.UserService + + // test data + TestUsername string + TestPassword string + TestNewPassword string +} + +func (t *Test) Setup(t2 *testing.T) { + var err error + t.userSvc, err = user.NewUserService() + if err != nil { + panic(err) + } + t2.Cleanup(t.Cleanup) +} + +func (t *Test) Cleanup() { + _ = t.modelSvc.GetBaseService(interfaces.ModelIdUser).DeleteList(nil) +} + +func NewTest() (t *Test, err error) { + // test + t = &Test{ + TestUsername: "test_username", + TestPassword: "test_password", + TestNewPassword: "test_new_password", + } + + // dependency injection + c := dig.New() + if err := c.Provide(service.GetService); err != nil { + return nil, err + } + if err := c.Invoke(func(modelSvc service.ModelService) { + t.modelSvc = modelSvc + }); err != nil { + return nil, err + } + + return t, nil +} diff --git a/core/user/test/user_service_test.go b/core/user/test/user_service_test.go new file mode 100644 index 00000000..0806c31e --- /dev/null +++ b/core/user/test/user_service_test.go @@ -0,0 +1,61 @@ +package test + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/utils" + "github.com/stretchr/testify/require" + "testing" +) + +func TestUserService_Init(t *testing.T) { + var err error + T.Setup(t) + + u, err := T.modelSvc.GetUserByUsernameWithPassword(constants.DefaultAdminUsername, nil) + require.Nil(t, err) + require.Equal(t, constants.DefaultAdminUsername, u.Username) + require.Equal(t, utils.EncryptMd5(constants.DefaultAdminPassword), u.Password) +} + +func TestUserService_Create_Login_CheckToken(t *testing.T) { + var err error + T.Setup(t) + + err = T.userSvc.Create(&interfaces.UserCreateOptions{ + Username: T.TestUsername, + Password: T.TestPassword, + }) + require.Nil(t, err) + + u, err := T.modelSvc.GetUserByUsernameWithPassword(T.TestUsername, nil) + require.Nil(t, err) + require.Equal(t, T.TestUsername, u.Username) + require.Equal(t, utils.EncryptMd5(T.TestPassword), u.Password) + + token, u2, err := T.userSvc.Login(&interfaces.UserLoginOptions{ + Username: T.TestUsername, + Password: T.TestPassword, + }) + require.Nil(t, err) + require.Greater(t, len(token), 10) + require.Equal(t, u.Username, u2.GetUsername()) + + u3, err := T.userSvc.CheckToken(token) + require.Nil(t, err) + require.Equal(t, u.Username, u3.GetUsername()) +} + +func TestUserService_ChangePassword(t *testing.T) { + var err error + T.Setup(t) + + u, err := T.modelSvc.GetUserByUsernameWithPassword(constants.DefaultAdminUsername, nil) + require.Nil(t, err) + err = T.userSvc.ChangePassword(u.Id, T.TestNewPassword) + require.Nil(t, err) + + u2, err := T.modelSvc.GetUserByUsernameWithPassword(constants.DefaultAdminUsername, nil) + require.Nil(t, err) + require.Equal(t, utils.EncryptMd5(T.TestNewPassword), u2.Password) +} diff --git a/core/utils/args.go b/core/utils/args.go new file mode 100644 index 00000000..e83f540a --- /dev/null +++ b/core/utils/args.go @@ -0,0 +1,17 @@ +package utils + +import "github.com/crawlab-team/crawlab/core/interfaces" + +func GetUserFromArgs(args ...interface{}) (u interfaces.User) { + for _, arg := range args { + switch arg.(type) { + case interfaces.User: + var ok bool + u, ok = arg.(interfaces.User) + if ok { + return u + } + } + } + return nil +} diff --git a/core/utils/array.go b/core/utils/array.go new file mode 100644 index 00000000..a5e958c6 --- /dev/null +++ b/core/utils/array.go @@ -0,0 +1,46 @@ +package utils + +import ( + "errors" + "math/rand" + "reflect" + "time" +) + +func StringArrayContains(arr []string, str string) bool { + for _, s := range arr { + if s == str { + return true + } + } + return false +} + +func GetArrayItems(array interface{}) (res []interface{}, err error) { + switch reflect.TypeOf(array).Kind() { + case reflect.Slice, reflect.Array: + s := reflect.ValueOf(array) + for i := 0; i < s.Len(); i++ { + obj, ok := s.Index(i).Interface().(interface{}) + if !ok { + return nil, errors.New("invalid type") + } + res = append(res, obj) + } + default: + return nil, errors.New("invalid type") + } + return res, nil +} + +func ShuffleArray(slice []interface{}) (err error) { + r := rand.New(rand.NewSource(time.Now().Unix())) + for len(slice) > 0 { + n := len(slice) + randIndex := r.Intn(n) + slice[n-1], slice[randIndex] = slice[randIndex], slice[n-1] + slice = slice[:n-1] + } + + return nil +} diff --git a/core/utils/backoff.go b/core/utils/backoff.go new file mode 100644 index 00000000..53658a8d --- /dev/null +++ b/core/utils/backoff.go @@ -0,0 +1,15 @@ +package utils + +import ( + "github.com/apex/log" + "github.com/cenkalti/backoff/v4" + "github.com/crawlab-team/go-trace" + "time" +) + +func BackoffErrorNotify(prefix string) backoff.Notify { + return func(err error, duration time.Duration) { + log.Errorf("%s error: %v. reattempt in %.1f seconds...", prefix, err, duration.Seconds()) + trace.PrintError(err) + } +} diff --git a/core/utils/binders/binder_col_name.go b/core/utils/binders/binder_col_name.go new file mode 100644 index 00000000..9e76224d --- /dev/null +++ b/core/utils/binders/binder_col_name.go @@ -0,0 +1,99 @@ +package binders + +import ( + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" +) + +func NewColNameBinder(id interfaces.ModelId) (b *ColNameBinder) { + return &ColNameBinder{id: id} +} + +type ColNameBinder struct { + id interfaces.ModelId +} + +func (b *ColNameBinder) Bind() (res interface{}, err error) { + switch b.id { + // system models + case interfaces.ModelIdArtifact: + return interfaces.ModelColNameArtifact, nil + case interfaces.ModelIdTag: + return interfaces.ModelColNameTag, nil + + // operation models + case interfaces.ModelIdNode: + return interfaces.ModelColNameNode, nil + case interfaces.ModelIdProject: + return interfaces.ModelColNameProject, nil + case interfaces.ModelIdSpider: + return interfaces.ModelColNameSpider, nil + case interfaces.ModelIdTask: + return interfaces.ModelColNameTask, nil + case interfaces.ModelIdJob: + return interfaces.ModelColNameJob, nil + case interfaces.ModelIdSchedule: + return interfaces.ModelColNameSchedule, nil + case interfaces.ModelIdUser: + return interfaces.ModelColNameUser, nil + case interfaces.ModelIdSetting: + return interfaces.ModelColNameSetting, nil + case interfaces.ModelIdToken: + return interfaces.ModelColNameToken, nil + case interfaces.ModelIdVariable: + return interfaces.ModelColNameVariable, nil + case interfaces.ModelIdTaskQueue: + return interfaces.ModelColNameTaskQueue, nil + case interfaces.ModelIdTaskStat: + return interfaces.ModelColNameTaskStat, nil + case interfaces.ModelIdSpiderStat: + return interfaces.ModelColNameSpiderStat, nil + case interfaces.ModelIdDataSource: + return interfaces.ModelColNameDataSource, nil + case interfaces.ModelIdDataCollection: + return interfaces.ModelColNameDataCollection, nil + case interfaces.ModelIdPassword: + return interfaces.ModelColNamePasswords, nil + case interfaces.ModelIdExtraValue: + return interfaces.ModelColNameExtraValues, nil + case interfaces.ModelIdGit: + return interfaces.ModelColNameGit, nil + case interfaces.ModelIdRole: + return interfaces.ModelColNameRole, nil + case interfaces.ModelIdUserRole: + return interfaces.ModelColNameUserRole, nil + case interfaces.ModelIdPermission: + return interfaces.ModelColNamePermission, nil + case interfaces.ModelIdRolePermission: + return interfaces.ModelColNameRolePermission, nil + case interfaces.ModelIdEnvironment: + return interfaces.ModelColNameEnvironment, nil + case interfaces.ModelIdDependencySetting: + return interfaces.ModelColNameDependencySetting, nil + + // invalid + default: + return res, errors.ErrorModelNotImplemented + } +} + +func (b *ColNameBinder) MustBind() (res interface{}) { + res, err := b.Bind() + if err != nil { + panic(err) + } + return res +} + +func (b *ColNameBinder) BindString() (res string, err error) { + res_, err := b.Bind() + if err != nil { + return "", err + } + res = res_.(string) + return res, nil +} + +func (b *ColNameBinder) MustBindString() (res string) { + return b.MustBind().(string) +} diff --git a/core/utils/bool.go b/core/utils/bool.go new file mode 100644 index 00000000..8d569683 --- /dev/null +++ b/core/utils/bool.go @@ -0,0 +1,12 @@ +package utils + +import "github.com/spf13/viper" + +func EnvIsTrue(key string, defaultOk bool) bool { + isTrueBool := viper.GetBool(key) + isTrueString := viper.GetString(key) + if isTrueString == "" { + return defaultOk + } + return isTrueBool || isTrueString == "Y" +} diff --git a/core/utils/bson.go b/core/utils/bson.go new file mode 100644 index 00000000..e6443e88 --- /dev/null +++ b/core/utils/bson.go @@ -0,0 +1,125 @@ +package utils + +import ( + "github.com/emirpasic/gods/sets/hashset" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "reflect" +) + +func BsonMEqual(v1, v2 bson.M) (ok bool) { + //ok = reflect.DeepEqual(v1, v2) + ok = bsonMEqual(v1, v2) + return ok +} + +func bsonMEqual(v1, v2 bson.M) (ok bool) { + // all keys + allKeys := hashset.New() + for key := range v1 { + allKeys.Add(key) + } + for key := range v2 { + allKeys.Add(key) + } + + for _, keyRes := range allKeys.Values() { + key := keyRes.(string) + v1Value, ok := v1[key] + if !ok { + return false + } + v2Value, ok := v2[key] + if !ok { + return false + } + + mode := 0 + + var v1ValueBsonM bson.M + var v1ValueBsonA bson.A + switch v1Value.(type) { + case bson.M: + mode = 1 + v1ValueBsonM = v1Value.(bson.M) + case bson.A: + mode = 2 + v1ValueBsonA = v1Value.(bson.A) + } + + var v2ValueBsonM bson.M + var v2ValueBsonA bson.A + switch v2Value.(type) { + case bson.M: + if mode != 1 { + return false + } + v2ValueBsonM = v2Value.(bson.M) + case bson.A: + if mode != 2 { + return false + } + v2ValueBsonA = v2Value.(bson.A) + } + + switch mode { + case 0: + if v1Value != v2Value { + return false + } + case 1: + if !bsonMEqual(v1ValueBsonM, v2ValueBsonM) { + return false + } + case 2: + if !reflect.DeepEqual(v1ValueBsonA, v2ValueBsonA) { + return false + } + default: + // not reachable + return false + } + } + + return true +} + +func NormalizeBsonMObjectId(m bson.M) (res bson.M) { + for k, v := range m { + switch v.(type) { + case string: + oid, err := primitive.ObjectIDFromHex(v.(string)) + if err == nil { + m[k] = oid + } + case bson.M: + m[k] = NormalizeBsonMObjectId(v.(bson.M)) + } + } + return m +} + +func DenormalizeBsonMObjectId(m bson.M) (res bson.M) { + for k, v := range m { + switch v.(type) { + case primitive.ObjectID: + m[k] = v.(primitive.ObjectID).Hex() + case bson.M: + m[k] = NormalizeBsonMObjectId(v.(bson.M)) + } + } + return m +} + +func NormalizeObjectId(v interface{}) (res interface{}) { + switch v.(type) { + case string: + oid, err := primitive.ObjectIDFromHex(v.(string)) + if err != nil { + return v + } + return oid + default: + return v + } +} diff --git a/core/utils/cache.go b/core/utils/cache.go new file mode 100644 index 00000000..b875b316 --- /dev/null +++ b/core/utils/cache.go @@ -0,0 +1,57 @@ +package utils + +import ( + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "go.mongodb.org/mongo-driver/bson" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "time" +) + +func GetFromDbCache(key string, getFn func() (string, error)) (res string, err error) { + col := mongo.GetMongoCol(constants.CacheColName) + + var d bson.M + if err := col.Find(bson.M{ + constants.CacheColKey: key, + }, nil).One(&d); err != nil { + if err != mongo2.ErrNoDocuments { + return "", err + } + + // get cache value + res, err = getFn() + if err != nil { + return "", err + } + + // save cache + d = bson.M{ + constants.CacheColKey: key, + constants.CacheColValue: res, + constants.CacheColTime: time.Now(), + } + if _, err := col.Insert(d); err != nil { + return "", err + } + return res, nil + } + + // type conversion + r, ok := d[constants.CacheColValue] + if !ok { + if err := col.Delete(bson.M{constants.CacheColKey: key}); err != nil { + return "", err + } + return GetFromDbCache(key, getFn) + } + res, ok = r.(string) + if !ok { + if err := col.Delete(bson.M{constants.CacheColKey: key}); err != nil { + return "", err + } + return GetFromDbCache(key, getFn) + } + + return res, nil +} diff --git a/core/utils/chan.go b/core/utils/chan.go new file mode 100644 index 00000000..c0144340 --- /dev/null +++ b/core/utils/chan.go @@ -0,0 +1,40 @@ +package utils + +import ( + "sync" +) + +var TaskExecChanMap = NewChanMap() + +type ChanMap struct { + m sync.Map +} + +func NewChanMap() *ChanMap { + return &ChanMap{m: sync.Map{}} +} + +func (cm *ChanMap) Chan(key string) chan string { + if ch, ok := cm.m.Load(key); ok { + return ch.(interface{}).(chan string) + } + ch := make(chan string, 10) + cm.m.Store(key, ch) + return ch +} + +func (cm *ChanMap) ChanBlocked(key string) chan string { + if ch, ok := cm.m.Load(key); ok { + return ch.(interface{}).(chan string) + } + ch := make(chan string) + cm.m.Store(key, ch) + return ch +} + +func (cm *ChanMap) HasChanKey(key string) bool { + if _, ok := cm.m.Load(key); ok { + return true + } + return false +} diff --git a/core/utils/chan_test.go b/core/utils/chan_test.go new file mode 100644 index 00000000..4bc75917 --- /dev/null +++ b/core/utils/chan_test.go @@ -0,0 +1,78 @@ +package utils + +import ( + . "github.com/smartystreets/goconvey/convey" + "sync" + "testing" +) + +func TestNewChanMap(t *testing.T) { + mapTest := sync.Map{} + chanTest := make(chan string) + test := "test" + + Convey("Call NewChanMap to generate ChanMap", t, func() { + mapTest.Store("test", chanTest) + chanMapTest := ChanMap{mapTest} + chanMap := NewChanMap() + chanMap.m.Store("test", chanTest) + + Convey(test, func() { + v1, ok := chanMap.m.Load("test") + So(ok, ShouldBeTrue) + v2, ok := chanMapTest.m.Load("test") + So(ok, ShouldBeTrue) + So(v1, ShouldResemble, v2) + }) + }) +} + +func TestChan(t *testing.T) { + mapTest := sync.Map{} + chanTest := make(chan string) + mapTest.Store("test", chanTest) + chanMapTest := ChanMap{mapTest} + + Convey("Test Chan use exist key", t, func() { + ch1 := chanMapTest.Chan("test") + Convey("ch1 should equal chanTest", func() { + So(ch1, ShouldEqual, chanTest) + }) + }) + Convey("Test Chan use no-exist key", t, func() { + ch2 := chanMapTest.Chan("test2") + Convey("ch2 should equal chanMapTest.m[test2]", func() { + v, ok := chanMapTest.m.Load("test2") + So(ok, ShouldBeTrue) + So(v, ShouldEqual, ch2) + }) + Convey("Cap of chanMapTest.m[test2] should equal 10", func() { + So(10, ShouldEqual, cap(ch2)) + }) + }) +} + +func TestChanBlocked(t *testing.T) { + mapTest := sync.Map{} + chanTest := make(chan string) + mapTest.Store("test", chanTest) + chanMapTest := ChanMap{mapTest} + + Convey("Test Chan use exist key", t, func() { + ch1 := chanMapTest.ChanBlocked("test") + Convey("ch1 should equal chanTest", func() { + So(ch1, ShouldEqual, chanTest) + }) + }) + Convey("Test Chan use no-exist key", t, func() { + ch2 := chanMapTest.ChanBlocked("test2") + Convey("ch2 should equal chanMapTest.m[test2]", func() { + v, ok := chanMapTest.m.Load("test2") + So(ok, ShouldBeTrue) + So(v, ShouldEqual, ch2) + }) + Convey("Cap of chanMapTest.m[test2] should equal 10", func() { + So(0, ShouldEqual, cap(ch2)) + }) + }) +} diff --git a/core/utils/cockroachdb.go b/core/utils/cockroachdb.go new file mode 100644 index 00000000..650c32f1 --- /dev/null +++ b/core/utils/cockroachdb.go @@ -0,0 +1,60 @@ +package utils + +import ( + "context" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/upper/db/v4" + "github.com/upper/db/v4/adapter/mssql" + "time" +) + +func GetCockroachdbSession(ds *models.DataSource) (s db.Session, err error) { + return getCockroachdbSession(context.Background(), ds) +} + +func GetCockroachdbSessionWithTimeout(ds *models.DataSource, timeout time.Duration) (s db.Session, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getCockroachdbSession(ctx, ds) +} + +func getCockroachdbSession(ctx context.Context, ds *models.DataSource) (s db.Session, err error) { + // normalize settings + host := ds.Host + port := ds.Port + if ds.Host == "" { + host = constants.DefaultHost + } + if ds.Port == "" { + port = constants.DefaultCockroachdbPort + } + + // connect settings + settings := mssql.ConnectionURL{ + User: ds.Username, + Password: ds.Password, + Database: ds.Database, + Host: fmt.Sprintf("%s:%s", host, port), + Options: nil, + } + + // session + done := make(chan struct{}) + go func() { + s, err = mssql.Open(settings) + close(done) + }() + + // wait for done + select { + case <-ctx.Done(): + if ctx.Err() != nil { + err = ctx.Err() + } + case <-done: + } + + return s, err +} diff --git a/core/utils/cron.go b/core/utils/cron.go new file mode 100644 index 00000000..e8838d2f --- /dev/null +++ b/core/utils/cron.go @@ -0,0 +1,176 @@ +package utils + +import ( + "fmt" + "math" + "strconv" + "strings" +) + +// cronBounds provides a range of acceptable values (plus a map of name to value). +type cronBounds struct { + min, max uint + names map[string]uint +} + +type cronUtils struct { + // The cronBounds for each field. + seconds cronBounds + minutes cronBounds + hours cronBounds + dom cronBounds + months cronBounds + dow cronBounds + + // Set the top bit if a star was included in the expression. + starBit uint64 +} + +// getRange returns the bits indicated by the given expression: +// number | number "-" number [ "/" number ] +// or error parsing range. +func (u *cronUtils) getRange(expr string, r cronBounds) (uint64, error) { + var ( + start, end, step uint + rangeAndStep = strings.Split(expr, "/") + lowAndHigh = strings.Split(rangeAndStep[0], "-") + singleDigit = len(lowAndHigh) == 1 + err error + ) + + var extra uint64 + if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { + start = r.min + end = r.max + extra = CronUtils.starBit + } else { + start, err = u.parseIntOrName(lowAndHigh[0], r.names) + if err != nil { + return 0, err + } + switch len(lowAndHigh) { + case 1: + end = start + case 2: + end, err = u.parseIntOrName(lowAndHigh[1], r.names) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("too many hyphens: %s", expr) + } + } + + switch len(rangeAndStep) { + case 1: + step = 1 + case 2: + step, err = u.mustParseInt(rangeAndStep[1]) + if err != nil { + return 0, err + } + + // Special handling: "N/step" means "N-max/step". + if singleDigit { + end = r.max + } + if step > 1 { + extra = 0 + } + default: + return 0, fmt.Errorf("too many slashes: %s", expr) + } + + if start < r.min { + return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr) + } + if end > r.max { + return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr) + } + if start > end { + return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr) + } + if step == 0 { + return 0, fmt.Errorf("step of range should be a positive number: %s", expr) + } + + return u.getBits(start, end, step) | extra, nil +} + +// parseIntOrName returns the (possibly-named) integer contained in expr. +func (u *cronUtils) parseIntOrName(expr string, names map[string]uint) (uint, error) { + if names != nil { + if namedInt, ok := names[strings.ToLower(expr)]; ok { + return namedInt, nil + } + } + return u.mustParseInt(expr) +} + +// mustParseInt parses the given expression as an int or returns an error. +func (u *cronUtils) mustParseInt(expr string) (uint, error) { + num, err := strconv.Atoi(expr) + if err != nil { + return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err) + } + if num < 0 { + return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr) + } + + return uint(num), nil +} + +// getBits sets all bits in the range [min, max], modulo the given step size. +func (u *cronUtils) getBits(min, max, step uint) uint64 { + var bits uint64 + + // If step is 1, use shifts. + if step == 1 { + return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) + } + + // Else, use a simple loop. + for i := min; i <= max; i += step { + bits |= 1 << i + } + return bits +} + +// all returns all bits within the given cronBounds. (plus the star bit) +func (u *cronUtils) all(r cronBounds) uint64 { + return u.getBits(r.min, r.max, 1) | CronUtils.starBit +} + +var CronUtils = cronUtils{ + // The cronBounds for each field. + seconds: cronBounds{0, 59, nil}, + minutes: cronBounds{0, 59, nil}, + hours: cronBounds{0, 23, nil}, + dom: cronBounds{1, 31, nil}, + months: cronBounds{1, 12, map[string]uint{ + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "may": 5, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, + }}, + dow: cronBounds{0, 6, map[string]uint{ + "sun": 0, + "mon": 1, + "tue": 2, + "wed": 3, + "thu": 4, + "fri": 5, + "sat": 6, + }}, + + // Set the top bit if a star was included in the expression. + starBit: 1 << 63, +} diff --git a/core/utils/debug.go b/core/utils/debug.go new file mode 100644 index 00000000..09ef3098 --- /dev/null +++ b/core/utils/debug.go @@ -0,0 +1,18 @@ +package utils + +import ( + "fmt" + "github.com/spf13/viper" + "time" +) + +func IsDebug() bool { + return viper.GetBool("debug") +} + +func LogDebug(msg string) { + if !IsDebug() { + return + } + fmt.Println(fmt.Sprintf("[DEBUG] %s: %s", time.Now().Format("2006-01-02 15:04:05"), msg)) +} diff --git a/core/utils/demo.go b/core/utils/demo.go new file mode 100644 index 00000000..19e31dc5 --- /dev/null +++ b/core/utils/demo.go @@ -0,0 +1,57 @@ +package utils + +import ( + "fmt" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/sys_exec" + "github.com/crawlab-team/go-trace" + "github.com/spf13/viper" +) + +func GetApiAddress() (res string) { + apiAddress := viper.GetString("api.address") + if apiAddress == "" { + return "http://localhost:8000" + } + return apiAddress +} + +func IsDemo() (ok bool) { + return EnvIsTrue("demo", true) +} + +func InitializedDemo() (ok bool) { + col := mongo.GetMongoCol("users") + n, err := col.Count(nil) + if err != nil { + return true + } + return n > 0 +} + +func ImportDemo() (err error) { + cmdStr := fmt.Sprintf("crawlab-cli login -a %s && crawlab-demo import", GetApiAddress()) + cmd := sys_exec.BuildCmd(cmdStr) + if err := cmd.Run(); err != nil { + trace.PrintError(err) + } + return nil +} + +func ReimportDemo() (err error) { + cmdStr := fmt.Sprintf("crawlab-cli login -a %s && crawlab-demo reimport", GetApiAddress()) + cmd := sys_exec.BuildCmd(cmdStr) + if err := cmd.Run(); err != nil { + trace.PrintError(err) + } + return nil +} + +func CleanupDemo() (err error) { + cmdStr := fmt.Sprintf("crawlab-cli login -a %s && crawlab-demo reimport", GetApiAddress()) + cmd := sys_exec.BuildCmd(cmdStr) + if err := cmd.Run(); err != nil { + trace.PrintError(err) + } + return nil +} diff --git a/core/utils/di.go b/core/utils/di.go new file mode 100644 index 00000000..8c467228 --- /dev/null +++ b/core/utils/di.go @@ -0,0 +1,18 @@ +package utils + +import ( + "github.com/crawlab-team/go-trace" + "github.com/spf13/viper" + "go.uber.org/dig" + "os" +) + +func VisualizeContainer(c *dig.Container) (err error) { + if !viper.GetBool("debug.di.visualize") { + return nil + } + if err := dig.Visualize(c, os.Stdout); err != nil { + return trace.TraceError(err) + } + return nil +} diff --git a/core/utils/docker.go b/core/utils/docker.go new file mode 100644 index 00000000..a1c98ed1 --- /dev/null +++ b/core/utils/docker.go @@ -0,0 +1,5 @@ +package utils + +func IsDocker() (ok bool) { + return EnvIsTrue("docker", false) +} diff --git a/core/utils/encrypt.go b/core/utils/encrypt.go new file mode 100644 index 00000000..d0754054 --- /dev/null +++ b/core/utils/encrypt.go @@ -0,0 +1,80 @@ +package utils + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/md5" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "io" +) + +func GetSecretKey() string { + return constants.DefaultEncryptServerKey +} + +func GetSecretKeyBytes() []byte { + return []byte(GetSecretKey()) +} + +func ComputeHmacSha256(message string, secret string) string { + key := []byte(secret) + h := hmac.New(sha256.New, key) + h.Write([]byte(message)) + sha := hex.EncodeToString(h.Sum(nil)) + return base64.StdEncoding.EncodeToString([]byte(sha)) +} + +func EncryptMd5(str string) string { + w := md5.New() + _, _ = io.WriteString(w, str) + md5str := fmt.Sprintf("%x", w.Sum(nil)) + return md5str +} + +func padding(src []byte, blockSize int) []byte { + padNum := blockSize - len(src)%blockSize + pad := bytes.Repeat([]byte{byte(padNum)}, padNum) + return append(src, pad...) +} + +func unPadding(src []byte) []byte { + n := len(src) + unPadNum := int(src[n-1]) + return src[:n-unPadNum] +} + +func EncryptAES(src string) (res string, err error) { + srcBytes := []byte(src) + key := GetSecretKeyBytes() + block, err := aes.NewCipher(key) + if err != nil { + return res, err + } + srcBytes = padding(srcBytes, block.BlockSize()) + blockMode := cipher.NewCBCEncrypter(block, key) + blockMode.CryptBlocks(srcBytes, srcBytes) + res = hex.EncodeToString(srcBytes) + return res, nil +} + +func DecryptAES(src string) (res string, err error) { + srcBytes, err := hex.DecodeString(src) + if err != nil { + return res, err + } + key := GetSecretKeyBytes() + block, err := aes.NewCipher(key) + if err != nil { + return res, err + } + blockMode := cipher.NewCBCDecrypter(block, key) + blockMode.CryptBlocks(srcBytes, srcBytes) + res = string(unPadding(srcBytes)) + return res, nil +} diff --git a/core/utils/encrypt_test.go b/core/utils/encrypt_test.go new file mode 100644 index 00000000..d393fa5b --- /dev/null +++ b/core/utils/encrypt_test.go @@ -0,0 +1,20 @@ +package utils + +import ( + "fmt" + "github.com/stretchr/testify/require" + "testing" +) + +func TestEncryptAesPassword(t *testing.T) { + plainText := "crawlab" + encryptedText, err := EncryptAES(plainText) + require.Nil(t, err) + decryptedText, err := DecryptAES(encryptedText) + require.Nil(t, err) + fmt.Println(fmt.Sprintf("plainText: %s", plainText)) + fmt.Println(fmt.Sprintf("encryptedText: %s", encryptedText)) + fmt.Println(fmt.Sprintf("decryptedText: %s", decryptedText)) + require.Equal(t, decryptedText, plainText) + require.NotEqual(t, decryptedText, encryptedText) +} diff --git a/core/utils/es.go b/core/utils/es.go new file mode 100644 index 00000000..587e1715 --- /dev/null +++ b/core/utils/es.go @@ -0,0 +1,159 @@ +package utils + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/cenkalti/backoff/v4" + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/crawlab-team/go-trace" + "github.com/elastic/go-elasticsearch/v8" + "github.com/elastic/go-elasticsearch/v8/esapi" + "go.mongodb.org/mongo-driver/bson/primitive" + "time" +) + +func GetElasticsearchClient(ds *models.DataSource) (c *elasticsearch.Client, err error) { + return getElasticsearchClient(context.Background(), ds) +} + +func GetElasticsearchClientWithTimeout(ds *models.DataSource, timeout time.Duration) (c *elasticsearch.Client, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getElasticsearchClient(ctx, ds) +} + +func getElasticsearchClient(ctx context.Context, ds *models.DataSource) (c *elasticsearch.Client, err error) { + // normalize settings + host := ds.Host + port := ds.Port + if ds.Host == "" { + host = constants.DefaultHost + } + if ds.Port == "" { + port = constants.DefaultElasticsearchPort + } + + // es hosts + addresses := []string{ + fmt.Sprintf("http://%s:%s", host, port), + } + + // retry backoff + rb := backoff.NewExponentialBackOff() + + // es client options + cfg := elasticsearch.Config{ + Addresses: addresses, + Username: ds.Username, + Password: ds.Password, + //CloudID: "", + //APIKey: "", + //ServiceToken: "", + //CertificateFingerprint: "", + //Header: nil, + //CACert: nil, + //RetryOnStatus: nil, + //DisableRetry: false, + //EnableRetryOnTimeout: false, + //MaxRetries: 0, + //CompressRequestBody: false, + //DiscoverNodesOnStart: false, + //DiscoverNodesInterval: 0, + //EnableMetrics: false, + //EnableDebugLogger: false, + //EnableCompatibilityMode: false, + //DisableMetaHeader: false, + //UseResponseCheckOnly: false, + RetryBackoff: func(i int) time.Duration { + if i == 1 { + rb.Reset() + } + return rb.NextBackOff() + }, + //Transport: nil, + //Logger: nil, + //Selector: nil, + //ConnectionPoolFunc: nil, + } + + // es client + done := make(chan struct{}) + go func() { + c, err = elasticsearch.NewClient(cfg) + if err != nil { + return + } + var res *esapi.Response + res, err = c.Info() + fmt.Println(res) + close(done) + }() + + // wait for done + select { + case <-ctx.Done(): + if ctx.Err() != nil { + err = ctx.Err() + } + case <-done: + } + + return c, err +} + +func GetElasticsearchQuery(query generic.ListQuery) (buf *bytes.Buffer) { + q := map[string]interface{}{} + if len(query) > 0 { + match := getElasticsearchQueryMatch(query) + q["query"] = map[string]interface{}{ + "match": match, + } + } + buf = &bytes.Buffer{} + if err := json.NewEncoder(buf).Encode(q); err != nil { + trace.PrintError(err) + } + return buf +} + +func GetElasticsearchQueryWithOptions(query generic.ListQuery, opts *generic.ListOptions) (buf *bytes.Buffer) { + q := map[string]interface{}{ + "size": opts.Limit, + "from": opts.Skip, + // TODO: sort + } + if len(query) > 0 { + match := getElasticsearchQueryMatch(query) + q["query"] = map[string]interface{}{ + "match": match, + } + } + buf = &bytes.Buffer{} + if err := json.NewEncoder(buf).Encode(q); err != nil { + trace.PrintError(err) + } + return buf +} + +func getElasticsearchQueryMatch(query generic.ListQuery) (match map[string]interface{}) { + match = map[string]interface{}{} + for _, c := range query { + switch c.Value.(type) { + case primitive.ObjectID: + c.Value = c.Value.(primitive.ObjectID).Hex() + } + switch c.Op { + case generic.OpEqual: + match[c.Key] = c.Value + default: + match[c.Key] = map[string]interface{}{ + c.Op: c.Value, + } + } + } + return match +} diff --git a/core/utils/file.go b/core/utils/file.go new file mode 100644 index 00000000..216a129a --- /dev/null +++ b/core/utils/file.go @@ -0,0 +1,382 @@ +package utils + +import ( + "archive/zip" + "crypto/md5" + "encoding/hex" + "fmt" + "github.com/apex/log" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "runtime/debug" +) + +func OpenFile(fileName string) *os.File { + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, os.ModePerm) + if err != nil { + log.Errorf("create file error: %s, file_name: %s", err.Error(), fileName) + debug.PrintStack() + return nil + } + return file +} + +func Exists(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + return os.IsExist(err) + } + return true +} + +func IsDir(path string) bool { + s, err := os.Stat(path) + if err != nil { + return false + } + return s.IsDir() +} + +// ListDir Add: 增加error类型作为第二返回值 +// 在其他函数如 /task/log/file_driver.go中的 *FileLogDriver.cleanup()函数调用时 +// 可以通过判断err是否为nil来判断是否有错误发生 +func ListDir(path string) ([]fs.FileInfo, error) { + list, err := os.ReadDir(path) + if err != nil { + log.Errorf(err.Error()) + debug.PrintStack() + return nil, err + } + + var res []fs.FileInfo + for _, item := range list { + info, err := item.Info() + if err != nil { + log.Errorf(err.Error()) + debug.PrintStack() + return nil, err + } + res = append(res, info) + } + return res, nil +} + +func DeCompress(srcFile *os.File, dstPath string) error { + // 如果保存路径不存在,创建一个 + if !Exists(dstPath) { + if err := os.MkdirAll(dstPath, os.ModePerm); err != nil { + debug.PrintStack() + return err + } + } + + // 读取zip文件 + zipFile, err := zip.OpenReader(srcFile.Name()) + if err != nil { + log.Errorf("Unzip File Error:" + err.Error()) + debug.PrintStack() + return err + } + defer Close(zipFile) + + // 遍历zip内所有文件和目录 + for _, innerFile := range zipFile.File { + // 获取该文件数据 + info := innerFile.FileInfo() + + // 如果是目录,则创建一个 + if info.IsDir() { + err = os.MkdirAll(filepath.Join(dstPath, innerFile.Name), os.ModeDir|os.ModePerm) + if err != nil { + log.Errorf("Unzip File Error : " + err.Error()) + debug.PrintStack() + return err + } + continue + } + + // 如果文件目录不存在,则创建一个 + dirPath := filepath.Join(dstPath, filepath.Dir(innerFile.Name)) + if !Exists(dirPath) { + if err = os.MkdirAll(dirPath, os.ModeDir|os.ModePerm); err != nil { + log.Errorf("Unzip File Error : " + err.Error()) + debug.PrintStack() + return err + } + } + + // 打开该文件 + srcFile, err := innerFile.Open() + if err != nil { + log.Errorf("Unzip File Error : " + err.Error()) + debug.PrintStack() + continue + } + + // 创建新文件 + newFilePath := filepath.Join(dstPath, innerFile.Name) + newFile, err := os.OpenFile(newFilePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode()) + if err != nil { + log.Errorf("Unzip File Error : " + err.Error()) + debug.PrintStack() + continue + } + + // 拷贝该文件到新文件中 + if _, err := io.Copy(newFile, srcFile); err != nil { + debug.PrintStack() + return err + } + + // 关闭该文件 + if err := srcFile.Close(); err != nil { + debug.PrintStack() + return err + } + + // 关闭新文件 + if err := newFile.Close(); err != nil { + debug.PrintStack() + return err + } + } + return nil +} + +// Compress 压缩文件 +// files 文件数组,可以是不同dir下的文件或者文件夹 +// dest 压缩文件存放地址 +func Compress(files []*os.File, dest string) error { + d, _ := os.Create(dest) + defer Close(d) + w := zip.NewWriter(d) + defer Close(w) + for _, file := range files { + if err := _Compress(file, "", w); err != nil { + return err + } + } + return nil +} + +func _Compress(file *os.File, prefix string, zw *zip.Writer) error { + info, err := file.Stat() + if err != nil { + debug.PrintStack() + return err + } + if info.IsDir() { + prefix = prefix + "/" + info.Name() + fileInfos, err := file.Readdir(-1) + if err != nil { + debug.PrintStack() + return err + } + for _, fi := range fileInfos { + f, err := os.Open(file.Name() + "/" + fi.Name()) + if err != nil { + debug.PrintStack() + return err + } + err = _Compress(f, prefix, zw) + if err != nil { + debug.PrintStack() + return err + } + } + } else { + header, err := zip.FileInfoHeader(info) + if err != nil { + debug.PrintStack() + return err + } + header.Name = prefix + "/" + header.Name + writer, err := zw.CreateHeader(header) + if err != nil { + debug.PrintStack() + return err + } + _, err = io.Copy(writer, file) + Close(file) + if err != nil { + debug.PrintStack() + return err + } + } + return nil +} + +func TrimFileData(data []byte) (res []byte) { + if string(data) == constants.EmptyFileData { + return res + } + return data +} + +func ZipDirectory(dir, zipfile string) error { + zipFile, err := os.Create(zipfile) + if err != nil { + return err + } + defer zipFile.Close() + + zipWriter := zip.NewWriter(zipFile) + defer zipWriter.Close() + + baseDir := filepath.Dir(dir) + + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + relPath, err := filepath.Rel(baseDir, path) + if err != nil { + return err + } + + zipFile, err := zipWriter.Create(relPath) + if err != nil { + return err + } + + file, err := os.Open(path) + if err != nil { + return err + } + defer file.Close() + + _, err = io.Copy(zipFile, file) + if err != nil { + return err + } + + return nil + }) + + return err +} + +// CopyFile File copies a single file from src to dst +func CopyFile(src, dst string) error { + var err error + var srcFd *os.File + var dstFd *os.File + var srcInfo os.FileInfo + + if srcFd, err = os.Open(src); err != nil { + return err + } + defer srcFd.Close() + + if dstFd, err = os.Create(dst); err != nil { + return err + } + defer dstFd.Close() + + if _, err = io.Copy(dstFd, srcFd); err != nil { + return err + } + if srcInfo, err = os.Stat(src); err != nil { + return err + } + return os.Chmod(dst, srcInfo.Mode()) +} + +// CopyDir Dir copies a whole directory recursively +func CopyDir(src string, dst string) error { + var err error + var fds []os.DirEntry + var srcInfo os.FileInfo + + if srcInfo, err = os.Stat(src); err != nil { + return err + } + + if err = os.MkdirAll(dst, srcInfo.Mode()); err != nil { + return err + } + + if fds, err = os.ReadDir(src); err != nil { + return err + } + for _, fd := range fds { + srcfp := path.Join(src, fd.Name()) + dstfp := path.Join(dst, fd.Name()) + + if fd.IsDir() { + if err = CopyDir(srcfp, dstfp); err != nil { + fmt.Println(err) + } + } else { + if err = CopyFile(srcfp, dstfp); err != nil { + fmt.Println(err) + } + } + } + return nil +} + +func GetFileHash(filePath string) (res string, err error) { + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + + hash := md5.New() + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + return hex.EncodeToString(hash.Sum(nil)), nil +} + +func ScanDirectory(dir string) (res map[string]entity.FsFileInfo, err error) { + files := make(map[string]entity.FsFileInfo) + + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + hash, err := GetFileHash(path) + if err != nil { + return err + } + + relPath, err := filepath.Rel(dir, path) + if err != nil { + return err + } + + files[relPath] = entity.FsFileInfo{ + Name: info.Name(), + Path: relPath, + FullPath: path, + Extension: filepath.Ext(path), + FileSize: info.Size(), + ModTime: info.ModTime(), + Mode: info.Mode(), + Hash: hash, + } + return nil + }) + if err != nil { + return nil, err + } + + return files, nil +} diff --git a/core/utils/file_test.go b/core/utils/file_test.go new file mode 100644 index 00000000..4af32d0d --- /dev/null +++ b/core/utils/file_test.go @@ -0,0 +1,129 @@ +package utils + +import ( + "archive/zip" + . "github.com/smartystreets/goconvey/convey" + "io" + "log" + "os" + "runtime/debug" + "testing" +) + +func TestExists(t *testing.T) { + var pathString = "../config" + var wrongPathString = "test" + + Convey("Test path or file is Exists or not", t, func() { + res := Exists(pathString) + Convey("The result should be true", func() { + So(res, ShouldEqual, true) + }) + wrongRes := Exists(wrongPathString) + Convey("The result should be false", func() { + So(wrongRes, ShouldEqual, false) + }) + }) +} + +func TestIsDir(t *testing.T) { + var pathString = "../config" + var fileString = "../config/config.go" + var wrongString = "test" + + Convey("Test path is folder or not", t, func() { + res := IsDir(pathString) + So(res, ShouldEqual, true) + fileRes := IsDir(fileString) + So(fileRes, ShouldEqual, false) + wrongRes := IsDir(wrongString) + So(wrongRes, ShouldEqual, false) + }) +} + +func TestCompress(t *testing.T) { + err := os.Mkdir("testCompress", os.ModePerm) + if err != nil { + t.Error("create testCompress failed") + } + var pathString = "testCompress" + var files []*os.File + var disPath = "testCompress" + file, err := os.Open(pathString) + if err != nil { + t.Error("open source path failed") + } + files = append(files, file) + Convey("Verify dispath is valid path", t, func() { + er := Compress(files, disPath) + Convey("err should be nil", func() { + So(er, ShouldEqual, nil) + }) + }) + _ = os.RemoveAll("testCompress") + +} +func Zip(zipFile string, fileList []string) error { + // 创建 zip 包文件 + fw, err := os.Create(zipFile) + if err != nil { + log.Fatal() + } + defer Close(fw) + + // 实例化新的 zip.Writer + zw := zip.NewWriter(fw) + defer Close(zw) + + for _, fileName := range fileList { + fr, err := os.Open(fileName) + if err != nil { + return err + } + fi, err := fr.Stat() + if err != nil { + return err + } + // 写入文件的头信息 + fh, err := zip.FileInfoHeader(fi) + if err != nil { + return err + } + w, err := zw.CreateHeader(fh) + if err != nil { + return err + } + // 写入文件内容 + _, err = io.Copy(w, fr) + if err != nil { + return err + } + } + return nil +} + +func TestDeCompress(t *testing.T) { + err := os.Mkdir("testDeCompress", os.ModePerm) + if err != nil { + t.Error(err) + + } + err = Zip("demo.zip", []string{}) + if err != nil { + t.Error("create zip file failed") + } + tmpFile, err := os.OpenFile("demo.zip", os.O_RDONLY, 0777) + if err != nil { + debug.PrintStack() + t.Error("open demo.zip failed") + } + var dstPath = "./testDeCompress" + Convey("Test DeCopmress func", t, func() { + + err := DeCompress(tmpFile, dstPath) + So(err, ShouldEqual, nil) + }) + _ = os.RemoveAll("testDeCompress") + _ = os.Remove("demo.zip") + +} diff --git a/core/utils/filter.go b/core/utils/filter.go new file mode 100644 index 00000000..13911bd3 --- /dev/null +++ b/core/utils/filter.go @@ -0,0 +1,49 @@ +package utils + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/errors" + "github.com/crawlab-team/crawlab/core/interfaces" + "go.mongodb.org/mongo-driver/bson" +) + +// FilterToQuery Translate entity.Filter to bson.M +func FilterToQuery(f interfaces.Filter) (q bson.M, err error) { + if f == nil || f.IsNil() { + return nil, nil + } + + q = bson.M{} + for _, cond := range f.GetConditions() { + key := cond.GetKey() + op := cond.GetOp() + value := cond.GetValue() + switch op { + case constants.FilterOpNotSet: + // do nothing + case constants.FilterOpEqual: + q[key] = cond.GetValue() + case constants.FilterOpNotEqual: + q[key] = bson.M{"$ne": value} + case constants.FilterOpContains, constants.FilterOpRegex, constants.FilterOpSearch: + q[key] = bson.M{"$regex": value, "$options": "i"} + case constants.FilterOpNotContains: + q[key] = bson.M{"$not": bson.M{"$regex": value}} + case constants.FilterOpIn: + q[key] = bson.M{"$in": value} + case constants.FilterOpNotIn: + q[key] = bson.M{"$nin": value} + case constants.FilterOpGreaterThan: + q[key] = bson.M{"$gt": value} + case constants.FilterOpGreaterThanEqual: + q[key] = bson.M{"$gte": value} + case constants.FilterOpLessThan: + q[key] = bson.M{"$lt": value} + case constants.FilterOpLessThanEqual: + q[key] = bson.M{"$lte": value} + default: + return nil, errors.ErrorFilterInvalidOperation + } + } + return q, nil +} diff --git a/core/utils/git.go b/core/utils/git.go new file mode 100644 index 00000000..1065aad5 --- /dev/null +++ b/core/utils/git.go @@ -0,0 +1,36 @@ +package utils + +import ( + vcs "github.com/crawlab-team/crawlab-vcs" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/interfaces" + "github.com/crawlab-team/crawlab/core/models/models" +) + +func InitGitClientAuth(g interfaces.Git, gitClient *vcs.GitClient) { + // set auth + switch g.GetAuthType() { + case constants.GitAuthTypeHttp: + gitClient.SetAuthType(vcs.GitAuthTypeHTTP) + gitClient.SetUsername(g.GetUsername()) + gitClient.SetPassword(g.GetPassword()) + case constants.GitAuthTypeSsh: + gitClient.SetAuthType(vcs.GitAuthTypeSSH) + gitClient.SetUsername(g.GetUsername()) + gitClient.SetPrivateKey(g.GetPassword()) + } +} + +func InitGitClientAuthV2(g *models.GitV2, gitClient *vcs.GitClient) { + // set auth + switch g.AuthType { + case constants.GitAuthTypeHttp: + gitClient.SetAuthType(vcs.GitAuthTypeHTTP) + gitClient.SetUsername(g.Username) + gitClient.SetPassword(g.Password) + case constants.GitAuthTypeSsh: + gitClient.SetAuthType(vcs.GitAuthTypeSSH) + gitClient.SetUsername(g.Username) + gitClient.SetPrivateKey(g.Password) + } +} diff --git a/core/utils/helpers.go b/core/utils/helpers.go new file mode 100644 index 00000000..338166f8 --- /dev/null +++ b/core/utils/helpers.go @@ -0,0 +1,36 @@ +package utils + +import ( + "github.com/crawlab-team/go-trace" + "io" + "reflect" + "unsafe" +) + +func BytesToString(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) +} + +func Close(c io.Closer) { + err := c.Close() + if err != nil { + trace.PrintError(err) + } +} + +func Contains(array interface{}, val interface{}) (fla bool) { + fla = false + switch reflect.TypeOf(array).Kind() { + case reflect.Slice: + { + s := reflect.ValueOf(array) + for i := 0; i < s.Len(); i++ { + if reflect.DeepEqual(val, s.Index(i).Interface()) { + fla = true + return + } + } + } + } + return +} diff --git a/core/utils/http.go b/core/utils/http.go new file mode 100644 index 00000000..3b2c0f66 --- /dev/null +++ b/core/utils/http.go @@ -0,0 +1,32 @@ +package utils + +import ( + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/entity" + "github.com/crawlab-team/go-trace" + "github.com/gin-gonic/gin" + "net/http" +) + +func handleError(statusCode int, c *gin.Context, err error, print bool) { + if print { + trace.PrintError(err) + } + c.AbortWithStatusJSON(statusCode, entity.Response{ + Status: constants.HttpResponseStatusOk, + Message: constants.HttpResponseMessageError, + Error: err.Error(), + }) +} + +func HandleError(statusCode int, c *gin.Context, err error) { + handleError(statusCode, c, err, true) +} + +func HandleErrorUnauthorized(c *gin.Context, err error) { + HandleError(http.StatusUnauthorized, c, err) +} + +func HandleErrorInternalServerError(c *gin.Context, err error) { + HandleError(http.StatusInternalServerError, c, err) +} diff --git a/core/utils/init.go b/core/utils/init.go new file mode 100644 index 00000000..21a12bd8 --- /dev/null +++ b/core/utils/init.go @@ -0,0 +1,30 @@ +package utils + +import ( + "github.com/crawlab-team/crawlab/core/interfaces" + "sync" +) + +var moduleInitializedMap = sync.Map{} + +func InitModule(id interfaces.ModuleId, fn func() error) (err error) { + res, ok := moduleInitializedMap.Load(id) + if ok { + initialized, _ := res.(bool) + if initialized { + return nil + } + } + + if err := fn(); err != nil { + return err + } + + moduleInitializedMap.Store(id, true) + + return nil +} + +func ForceInitModule(fn func() error) (err error) { + return fn() +} diff --git a/core/utils/json.go b/core/utils/json.go new file mode 100644 index 00000000..564a67eb --- /dev/null +++ b/core/utils/json.go @@ -0,0 +1,12 @@ +package utils + +import "encoding/json" + +func JsonToBytes(d interface{}) (bytes []byte, err error) { + switch d.(type) { + case []byte: + return d.([]byte), nil + default: + return json.Marshal(d) + } +} diff --git a/core/utils/kafka.go b/core/utils/kafka.go new file mode 100644 index 00000000..1bf005f2 --- /dev/null +++ b/core/utils/kafka.go @@ -0,0 +1,41 @@ +package utils + +import ( + "context" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/segmentio/kafka-go" + "time" +) + +func GetKafkaConnection(ds *models.DataSource) (c *kafka.Conn, err error) { + return getKafkaConnection(context.Background(), ds) +} + +func GetKafkaConnectionWithTimeout(ds *models.DataSource, timeout time.Duration) (c *kafka.Conn, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getKafkaConnection(ctx, ds) +} + +func getKafkaConnection(ctx context.Context, ds *models.DataSource) (c *kafka.Conn, err error) { + // normalize settings + host := ds.Host + port := ds.Port + if ds.Host == "" { + host = constants.DefaultHost + } + if ds.Port == "" { + port = constants.DefaultKafkaPort + } + + // kafka connection address + network := "tcp" + address := fmt.Sprintf("%s:%s", host, port) + topic := ds.Database + partition := 0 // TODO: parameterize + + // kafka connection + return kafka.DialLeader(ctx, network, address, topic, partition) +} diff --git a/core/utils/mongo.go b/core/utils/mongo.go new file mode 100644 index 00000000..4b1ff502 --- /dev/null +++ b/core/utils/mongo.go @@ -0,0 +1,94 @@ +package utils + +import ( + "context" + "github.com/crawlab-team/crawlab-db/generic" + "github.com/crawlab-team/crawlab-db/mongo" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "go.mongodb.org/mongo-driver/bson" + mongo2 "go.mongodb.org/mongo-driver/mongo" + "time" +) + +func GetMongoQuery(query generic.ListQuery) (res bson.M) { + res = bson.M{} + for _, c := range query { + switch c.Op { + case generic.OpEqual: + res[c.Key] = c.Value + default: + res[c.Key] = bson.M{ + c.Op: c.Value, + } + } + } + return res +} + +func GetMongoOpts(opts *generic.ListOptions) (res *mongo.FindOptions) { + var sort bson.D + for _, s := range opts.Sort { + direction := 1 + if s.Direction == generic.SortDirectionAsc { + direction = 1 + } else if s.Direction == generic.SortDirectionDesc { + direction = -1 + } + sort = append(sort, bson.E{Key: s.Key, Value: direction}) + } + return &mongo.FindOptions{ + Skip: opts.Skip, + Limit: opts.Limit, + Sort: sort, + } +} + +func GetMongoClient(ds *models.DataSource) (c *mongo2.Client, err error) { + return getMongoClient(context.Background(), ds) +} + +func GetMongoClientWithTimeout(ds *models.DataSource, timeout time.Duration) (c *mongo2.Client, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getMongoClient(ctx, ds) +} + +func getMongoClient(ctx context.Context, ds *models.DataSource) (c *mongo2.Client, err error) { + // normalize settings + if ds.Host == "" { + ds.Host = constants.DefaultHost + } + if ds.Port == "" { + ds.Port = constants.DefaultMongoPort + } + + // options + var opts []mongo.ClientOption + opts = append(opts, mongo.WithContext(ctx)) + opts = append(opts, mongo.WithUri(ds.Url)) + opts = append(opts, mongo.WithHost(ds.Host)) + opts = append(opts, mongo.WithPort(ds.Port)) + opts = append(opts, mongo.WithDb(ds.Database)) + opts = append(opts, mongo.WithUsername(ds.Username)) + opts = append(opts, mongo.WithPassword(ds.Password)) + opts = append(opts, mongo.WithHosts(ds.Hosts)) + + // extra + if ds.Extra != nil { + // auth source + authSource, ok := ds.Extra["auth_source"] + if ok { + opts = append(opts, mongo.WithAuthSource(authSource)) + } + + // auth mechanism + authMechanism, ok := ds.Extra["auth_mechanism"] + if ok { + opts = append(opts, mongo.WithAuthMechanism(authMechanism)) + } + } + + // client + return mongo.GetMongoClient(opts...) +} diff --git a/core/utils/mssql.go b/core/utils/mssql.go new file mode 100644 index 00000000..0fb21353 --- /dev/null +++ b/core/utils/mssql.go @@ -0,0 +1,60 @@ +package utils + +import ( + "context" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/upper/db/v4" + "github.com/upper/db/v4/adapter/mssql" + "time" +) + +func GetMssqlSession(ds *models.DataSource) (s db.Session, err error) { + return getMssqlSession(context.Background(), ds) +} + +func GetMssqlSessionWithTimeout(ds *models.DataSource, timeout time.Duration) (s db.Session, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getMssqlSession(ctx, ds) +} + +func getMssqlSession(ctx context.Context, ds *models.DataSource) (s db.Session, err error) { + // normalize settings + host := ds.Host + port := ds.Port + if ds.Host == "" { + host = constants.DefaultHost + } + if ds.Port == "" { + port = constants.DefaultMssqlPort + } + + // connect settings + settings := mssql.ConnectionURL{ + User: ds.Username, + Password: ds.Password, + Database: ds.Database, + Host: fmt.Sprintf("%s:%s", host, port), + Options: nil, + } + + // session + done := make(chan struct{}) + go func() { + s, err = mssql.Open(settings) + close(done) + }() + + // wait for done + select { + case <-ctx.Done(): + if ctx.Err() != nil { + err = ctx.Err() + } + case <-done: + } + + return s, err +} diff --git a/core/utils/mysql.go b/core/utils/mysql.go new file mode 100644 index 00000000..8fc9ae18 --- /dev/null +++ b/core/utils/mysql.go @@ -0,0 +1,60 @@ +package utils + +import ( + "context" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/upper/db/v4" + "github.com/upper/db/v4/adapter/mysql" + "time" +) + +func GetMysqlSession(ds *models.DataSource) (s db.Session, err error) { + return getMysqlSession(context.Background(), ds) +} + +func GetMysqlSessionWithTimeout(ds *models.DataSource, timeout time.Duration) (s db.Session, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getMysqlSession(ctx, ds) +} + +func getMysqlSession(ctx context.Context, ds *models.DataSource) (s db.Session, err error) { + // normalize settings + host := ds.Host + port := ds.Port + if ds.Host == "" { + host = constants.DefaultHost + } + if ds.Port == "" { + port = constants.DefaultMysqlPort + } + + // connect settings + settings := mysql.ConnectionURL{ + User: ds.Username, + Password: ds.Password, + Database: ds.Database, + Host: fmt.Sprintf("%s:%s", host, port), + Options: nil, + } + + // session + done := make(chan struct{}) + go func() { + s, err = mysql.Open(settings) + close(done) + }() + + // wait for done + select { + case <-ctx.Done(): + if ctx.Err() != nil { + err = ctx.Err() + } + case <-done: + } + + return s, err +} diff --git a/core/utils/node.go b/core/utils/node.go new file mode 100644 index 00000000..1f20ade1 --- /dev/null +++ b/core/utils/node.go @@ -0,0 +1,13 @@ +package utils + +func IsMaster() bool { + return EnvIsTrue("node.master", false) +} + +func GetNodeType() string { + if IsMaster() { + return "master" + } else { + return "worker" + } +} diff --git a/core/utils/os.go b/core/utils/os.go new file mode 100644 index 00000000..18351252 --- /dev/null +++ b/core/utils/os.go @@ -0,0 +1,13 @@ +package utils + +import ( + "os" + "os/signal" + "syscall" +) + +func DefaultWait() { + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit +} diff --git a/core/utils/postgresql.go b/core/utils/postgresql.go new file mode 100644 index 00000000..40c8a208 --- /dev/null +++ b/core/utils/postgresql.go @@ -0,0 +1,60 @@ +package utils + +import ( + "context" + "fmt" + "github.com/crawlab-team/crawlab/core/constants" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/upper/db/v4" + "github.com/upper/db/v4/adapter/postgresql" + "time" +) + +func GetPostgresqlSession(ds *models.DataSource) (s db.Session, err error) { + return getPostgresqlSession(context.Background(), ds) +} + +func GetPostgresqlSessionWithTimeout(ds *models.DataSource, timeout time.Duration) (s db.Session, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getPostgresqlSession(ctx, ds) +} + +func getPostgresqlSession(ctx context.Context, ds *models.DataSource) (s db.Session, err error) { + // normalize settings + host := ds.Host + port := ds.Port + if ds.Host == "" { + host = constants.DefaultHost + } + if ds.Port == "" { + port = constants.DefaultPostgresqlPort + } + + // connect settings + settings := postgresql.ConnectionURL{ + User: ds.Username, + Password: ds.Password, + Database: ds.Database, + Host: fmt.Sprintf("%s:%s", host, port), + Options: nil, + } + + // session + done := make(chan struct{}) + go func() { + s, err = postgresql.Open(settings) + close(done) + }() + + // wait for done + select { + case <-ctx.Done(): + if ctx.Err() != nil { + err = ctx.Err() + } + case <-done: + } + + return s, err +} diff --git a/core/utils/result.go b/core/utils/result.go new file mode 100644 index 00000000..62450309 --- /dev/null +++ b/core/utils/result.go @@ -0,0 +1,23 @@ +package utils + +import ( + "encoding/json" + "github.com/crawlab-team/crawlab/core/interfaces" +) + +func GetResultHash(value interface{}, keys []string) (res string, err error) { + m := make(map[string]interface{}) + for _, k := range keys { + _value, ok := value.(interfaces.Result) + if !ok { + continue + } + v := _value.GetValue(k) + m[k] = v + } + data, err := json.Marshal(m) + if err != nil { + return "", err + } + return EncryptMd5(string(data)), nil +} diff --git a/core/utils/rpc.go b/core/utils/rpc.go new file mode 100644 index 00000000..03414199 --- /dev/null +++ b/core/utils/rpc.go @@ -0,0 +1,14 @@ +package utils + +import "encoding/json" + +// Object 转化为 String +func ObjectToString(params interface{}) string { + bytes, _ := json.Marshal(params) + return BytesToString(bytes) +} + +// 获取 RPC 参数 +func GetRpcParam(key string, params map[string]string) string { + return params[key] +} diff --git a/core/utils/spider.go b/core/utils/spider.go new file mode 100644 index 00000000..4484ccf0 --- /dev/null +++ b/core/utils/spider.go @@ -0,0 +1,8 @@ +package utils + +func GetSpiderCol(col string, name string) string { + if col == "" { + return "results_" + name + } + return col +} diff --git a/core/utils/sql.go b/core/utils/sql.go new file mode 100644 index 00000000..f231d755 --- /dev/null +++ b/core/utils/sql.go @@ -0,0 +1,27 @@ +package utils + +import ( + "github.com/crawlab-team/crawlab-db/generic" + "github.com/upper/db/v4" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +func GetSqlQuery(query generic.ListQuery) (res db.Cond) { + res = db.Cond{} + for _, c := range query { + switch c.Value.(type) { + case primitive.ObjectID: + c.Value = c.Value.(primitive.ObjectID).Hex() + } + switch c.Op { + case generic.OpEqual: + res[c.Key] = c.Value + default: + res[c.Key] = db.Cond{ + c.Op: c.Value, + } + } + } + // TODO: sort + return res +} diff --git a/core/utils/sqlite.go b/core/utils/sqlite.go new file mode 100644 index 00000000..1d6ff682 --- /dev/null +++ b/core/utils/sqlite.go @@ -0,0 +1,45 @@ +package utils + +import ( + "context" + "github.com/crawlab-team/crawlab/core/models/models" + "github.com/upper/db/v4" + "github.com/upper/db/v4/adapter/sqlite" + "time" +) + +func GetSqliteSession(ds *models.DataSource) (s db.Session, err error) { + return getSqliteSession(context.Background(), ds) +} + +func GetSqliteSessionWithTimeout(ds *models.DataSource, timeout time.Duration) (s db.Session, err error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + return getSqliteSession(ctx, ds) +} + +func getSqliteSession(ctx context.Context, ds *models.DataSource) (s db.Session, err error) { + // connect settings + settings := sqlite.ConnectionURL{ + Database: ds.Database, + Options: nil, + } + + // session + done := make(chan struct{}) + go func() { + s, err = sqlite.Open(settings) + close(done) + }() + + // wait for done + select { + case <-ctx.Done(): + if ctx.Err() != nil { + err = ctx.Err() + } + case <-done: + } + + return s, err +} diff --git a/core/utils/stats.go b/core/utils/stats.go new file mode 100644 index 00000000..d4b585bf --- /dev/null +++ b/core/utils/stats.go @@ -0,0 +1 @@ +package utils diff --git a/core/utils/system.go b/core/utils/system.go new file mode 100644 index 00000000..9b161f11 --- /dev/null +++ b/core/utils/system.go @@ -0,0 +1,7 @@ +package utils + +import "github.com/spf13/viper" + +func IsPro() bool { + return viper.GetString("info.edition") == "global.edition.pro" +} diff --git a/core/utils/task.go b/core/utils/task.go new file mode 100644 index 00000000..94bfbb3b --- /dev/null +++ b/core/utils/task.go @@ -0,0 +1,13 @@ +package utils + +import "github.com/crawlab-team/crawlab/core/constants" + +func IsCancellable(status string) bool { + switch status { + case constants.TaskStatusPending, + constants.TaskStatusRunning: + return true + default: + return false + } +} diff --git a/core/utils/time.go b/core/utils/time.go new file mode 100644 index 00000000..689e2c28 --- /dev/null +++ b/core/utils/time.go @@ -0,0 +1,18 @@ +package utils + +import ( + "time" +) + +func GetLocalTime(t time.Time) time.Time { + return t.In(time.Local) +} + +func GetTimeString(t time.Time) string { + return t.Format("2006-01-02 15:04:05") +} + +func GetLocalTimeString(t time.Time) string { + t = GetLocalTime(t) + return GetTimeString(t) +} diff --git a/core/utils/uuid.go b/core/utils/uuid.go new file mode 100644 index 00000000..72c5191c --- /dev/null +++ b/core/utils/uuid.go @@ -0,0 +1,8 @@ +package utils + +import "github.com/google/uuid" + +func NewUUIDString() (res string) { + id, _ := uuid.NewUUID() + return id.String() +} diff --git a/grpc/.gitignore b/grpc/.gitignore new file mode 100644 index 00000000..f32e31af --- /dev/null +++ b/grpc/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.DS_Store diff --git a/grpc/LICENSE b/grpc/LICENSE new file mode 100644 index 00000000..3c0946b9 --- /dev/null +++ b/grpc/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2021, Crawlab Team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/grpc/README.md b/grpc/README.md new file mode 100644 index 00000000..207b4216 --- /dev/null +++ b/grpc/README.md @@ -0,0 +1,2 @@ +# crawlab-grpc +gRPC for Crawlab diff --git a/grpc/bin/compile.sh b/grpc/bin/compile.sh new file mode 100755 index 00000000..b0d5c65f --- /dev/null +++ b/grpc/bin/compile.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +if [ -L $0 ] +then + BASE_DIR=`dirname $(readlink $0)` +else + BASE_DIR=`dirname $0` +fi +base_path=$(cd $BASE_DIR/..; pwd) + +cd $base_path && \ + rm -rf dist | true + +cd $base_path && \ + mkdir -p dist/python | true && \ + mkdir -p dist/js | true && \ + mkdir -p dist/ts | true && \ + mkdir -p dist/java | true && \ + mkdir -p dist/csharp | true && \ + mkdir -p dist/php | true && \ + mkdir -p dist/ruby | true + +cd $base_path && \ + protoc -I ./proto \ + --go_out=. \ + --go-grpc_out=. \ + --python_out=dist/python \ + --js_out=dist/js \ + --java_out=dist/java \ + --csharp_out=dist/csharp \ + ./proto/**/*.proto + +# python +cd $base_path && \ + python3 -m grpc_tools.protoc -I ./proto \ + --grpc_python_out=dist/python \ + ./proto/**/*.proto diff --git a/grpc/dependencies_service_v2_request.pb.go b/grpc/dependencies_service_v2_request.pb.go new file mode 100644 index 00000000..cae03422 --- /dev/null +++ b/grpc/dependencies_service_v2_request.pb.go @@ -0,0 +1,488 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/dependencies_service_v2_request.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Dependency struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"` +} + +func (x *Dependency) Reset() { + *x = Dependency{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Dependency) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Dependency) ProtoMessage() {} + +func (x *Dependency) ProtoReflect() protoreflect.Message { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Dependency.ProtoReflect.Descriptor instead. +func (*Dependency) Descriptor() ([]byte, []int) { + return file_entity_dependencies_service_v2_request_proto_rawDescGZIP(), []int{0} +} + +func (x *Dependency) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Dependency) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +type DependenciesServiceV2ConnectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` +} + +func (x *DependenciesServiceV2ConnectRequest) Reset() { + *x = DependenciesServiceV2ConnectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DependenciesServiceV2ConnectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DependenciesServiceV2ConnectRequest) ProtoMessage() {} + +func (x *DependenciesServiceV2ConnectRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DependenciesServiceV2ConnectRequest.ProtoReflect.Descriptor instead. +func (*DependenciesServiceV2ConnectRequest) Descriptor() ([]byte, []int) { + return file_entity_dependencies_service_v2_request_proto_rawDescGZIP(), []int{1} +} + +func (x *DependenciesServiceV2ConnectRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +type DependenciesServiceV2SyncRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + Lang string `protobuf:"bytes,2,opt,name=lang,proto3" json:"lang,omitempty"` + Dependencies []*Dependency `protobuf:"bytes,3,rep,name=dependencies,proto3" json:"dependencies,omitempty"` +} + +func (x *DependenciesServiceV2SyncRequest) Reset() { + *x = DependenciesServiceV2SyncRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DependenciesServiceV2SyncRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DependenciesServiceV2SyncRequest) ProtoMessage() {} + +func (x *DependenciesServiceV2SyncRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DependenciesServiceV2SyncRequest.ProtoReflect.Descriptor instead. +func (*DependenciesServiceV2SyncRequest) Descriptor() ([]byte, []int) { + return file_entity_dependencies_service_v2_request_proto_rawDescGZIP(), []int{2} +} + +func (x *DependenciesServiceV2SyncRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *DependenciesServiceV2SyncRequest) GetLang() string { + if x != nil { + return x.Lang + } + return "" +} + +func (x *DependenciesServiceV2SyncRequest) GetDependencies() []*Dependency { + if x != nil { + return x.Dependencies + } + return nil +} + +type DependenciesServiceV2InstallRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + Lang string `protobuf:"bytes,2,opt,name=lang,proto3" json:"lang,omitempty"` + Dependencies []*Dependency `protobuf:"bytes,3,rep,name=dependencies,proto3" json:"dependencies,omitempty"` + Proxy string `protobuf:"bytes,4,opt,name=proxy,proto3" json:"proxy,omitempty"` +} + +func (x *DependenciesServiceV2InstallRequest) Reset() { + *x = DependenciesServiceV2InstallRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DependenciesServiceV2InstallRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DependenciesServiceV2InstallRequest) ProtoMessage() {} + +func (x *DependenciesServiceV2InstallRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DependenciesServiceV2InstallRequest.ProtoReflect.Descriptor instead. +func (*DependenciesServiceV2InstallRequest) Descriptor() ([]byte, []int) { + return file_entity_dependencies_service_v2_request_proto_rawDescGZIP(), []int{3} +} + +func (x *DependenciesServiceV2InstallRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *DependenciesServiceV2InstallRequest) GetLang() string { + if x != nil { + return x.Lang + } + return "" +} + +func (x *DependenciesServiceV2InstallRequest) GetDependencies() []*Dependency { + if x != nil { + return x.Dependencies + } + return nil +} + +func (x *DependenciesServiceV2InstallRequest) GetProxy() string { + if x != nil { + return x.Proxy + } + return "" +} + +type DependenciesServiceV2UninstallRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + Lang string `protobuf:"bytes,2,opt,name=lang,proto3" json:"lang,omitempty"` + Dependencies []*Dependency `protobuf:"bytes,3,rep,name=dependencies,proto3" json:"dependencies,omitempty"` +} + +func (x *DependenciesServiceV2UninstallRequest) Reset() { + *x = DependenciesServiceV2UninstallRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DependenciesServiceV2UninstallRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DependenciesServiceV2UninstallRequest) ProtoMessage() {} + +func (x *DependenciesServiceV2UninstallRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_dependencies_service_v2_request_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DependenciesServiceV2UninstallRequest.ProtoReflect.Descriptor instead. +func (*DependenciesServiceV2UninstallRequest) Descriptor() ([]byte, []int) { + return file_entity_dependencies_service_v2_request_proto_rawDescGZIP(), []int{4} +} + +func (x *DependenciesServiceV2UninstallRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *DependenciesServiceV2UninstallRequest) GetLang() string { + if x != nil { + return x.Lang + } + return "" +} + +func (x *DependenciesServiceV2UninstallRequest) GetDependencies() []*Dependency { + if x != nil { + return x.Dependencies + } + return nil +} + +var File_entity_dependencies_service_v2_request_proto protoreflect.FileDescriptor + +var file_entity_dependencies_service_v2_request_proto_rawDesc = []byte{ + 0x0a, 0x2c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x69, 0x65, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x32, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, + 0x67, 0x72, 0x70, 0x63, 0x22, 0x3a, 0x0a, 0x0a, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0x40, 0x0a, 0x23, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, + 0x65, 0x79, 0x22, 0x87, 0x01, 0x0a, 0x20, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x53, 0x79, 0x6e, 0x63, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, + 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, + 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0c, + 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x22, 0xa0, 0x01, 0x0a, + 0x23, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6c, + 0x61, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, + 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0c, 0x64, 0x65, 0x70, + 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x6f, + 0x78, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x22, + 0x8c, 0x01, 0x0a, 0x25, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x55, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, + 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6c, 0x61, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x0c, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, + 0x52, 0x0c, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x42, 0x08, + 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_dependencies_service_v2_request_proto_rawDescOnce sync.Once + file_entity_dependencies_service_v2_request_proto_rawDescData = file_entity_dependencies_service_v2_request_proto_rawDesc +) + +func file_entity_dependencies_service_v2_request_proto_rawDescGZIP() []byte { + file_entity_dependencies_service_v2_request_proto_rawDescOnce.Do(func() { + file_entity_dependencies_service_v2_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_dependencies_service_v2_request_proto_rawDescData) + }) + return file_entity_dependencies_service_v2_request_proto_rawDescData +} + +var file_entity_dependencies_service_v2_request_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_entity_dependencies_service_v2_request_proto_goTypes = []interface{}{ + (*Dependency)(nil), // 0: grpc.Dependency + (*DependenciesServiceV2ConnectRequest)(nil), // 1: grpc.DependenciesServiceV2ConnectRequest + (*DependenciesServiceV2SyncRequest)(nil), // 2: grpc.DependenciesServiceV2SyncRequest + (*DependenciesServiceV2InstallRequest)(nil), // 3: grpc.DependenciesServiceV2InstallRequest + (*DependenciesServiceV2UninstallRequest)(nil), // 4: grpc.DependenciesServiceV2UninstallRequest +} +var file_entity_dependencies_service_v2_request_proto_depIdxs = []int32{ + 0, // 0: grpc.DependenciesServiceV2SyncRequest.dependencies:type_name -> grpc.Dependency + 0, // 1: grpc.DependenciesServiceV2InstallRequest.dependencies:type_name -> grpc.Dependency + 0, // 2: grpc.DependenciesServiceV2UninstallRequest.dependencies:type_name -> grpc.Dependency + 3, // [3:3] is the sub-list for method output_type + 3, // [3:3] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_entity_dependencies_service_v2_request_proto_init() } +func file_entity_dependencies_service_v2_request_proto_init() { + if File_entity_dependencies_service_v2_request_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_entity_dependencies_service_v2_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Dependency); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_dependencies_service_v2_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DependenciesServiceV2ConnectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_dependencies_service_v2_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DependenciesServiceV2SyncRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_dependencies_service_v2_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DependenciesServiceV2InstallRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_dependencies_service_v2_request_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DependenciesServiceV2UninstallRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_dependencies_service_v2_request_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_dependencies_service_v2_request_proto_goTypes, + DependencyIndexes: file_entity_dependencies_service_v2_request_proto_depIdxs, + MessageInfos: file_entity_dependencies_service_v2_request_proto_msgTypes, + }.Build() + File_entity_dependencies_service_v2_request_proto = out.File + file_entity_dependencies_service_v2_request_proto_rawDesc = nil + file_entity_dependencies_service_v2_request_proto_goTypes = nil + file_entity_dependencies_service_v2_request_proto_depIdxs = nil +} diff --git a/grpc/dependency_service_v2.pb.go b/grpc/dependency_service_v2.pb.go new file mode 100644 index 00000000..7057a9cb --- /dev/null +++ b/grpc/dependency_service_v2.pb.go @@ -0,0 +1,108 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/dependency_service_v2.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_dependency_service_v2_proto protoreflect.FileDescriptor + +var file_services_dependency_service_v2_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x64, 0x65, 0x70, 0x65, 0x6e, + 0x64, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x32, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x2c, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x2f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, + 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x32, 0x5f, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x32, 0xc9, 0x02, 0x0a, 0x13, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x12, 0x48, 0x0a, 0x07, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x12, 0x29, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, + 0x32, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x28, 0x01, 0x12, 0x40, 0x0a, 0x04, 0x53, 0x79, 0x6e, 0x63, 0x12, 0x26, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x53, 0x79, 0x6e, 0x63, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4a, 0x0a, 0x07, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x12, 0x29, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, + 0x63, 0x69, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x73, + 0x74, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, + 0x01, 0x12, 0x5a, 0x0a, 0x15, 0x55, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x44, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x12, 0x2b, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x55, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, + 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_services_dependency_service_v2_proto_goTypes = []interface{}{ + (*DependenciesServiceV2ConnectRequest)(nil), // 0: grpc.DependenciesServiceV2ConnectRequest + (*DependenciesServiceV2SyncRequest)(nil), // 1: grpc.DependenciesServiceV2SyncRequest + (*DependenciesServiceV2InstallRequest)(nil), // 2: grpc.DependenciesServiceV2InstallRequest + (*DependenciesServiceV2UninstallRequest)(nil), // 3: grpc.DependenciesServiceV2UninstallRequest + (*Response)(nil), // 4: grpc.Response +} +var file_services_dependency_service_v2_proto_depIdxs = []int32{ + 0, // 0: grpc.DependencyServiceV2.Connect:input_type -> grpc.DependenciesServiceV2ConnectRequest + 1, // 1: grpc.DependencyServiceV2.Sync:input_type -> grpc.DependenciesServiceV2SyncRequest + 2, // 2: grpc.DependencyServiceV2.Install:input_type -> grpc.DependenciesServiceV2InstallRequest + 3, // 3: grpc.DependencyServiceV2.UninstallDependencies:input_type -> grpc.DependenciesServiceV2UninstallRequest + 4, // 4: grpc.DependencyServiceV2.Connect:output_type -> grpc.Response + 4, // 5: grpc.DependencyServiceV2.Sync:output_type -> grpc.Response + 4, // 6: grpc.DependencyServiceV2.Install:output_type -> grpc.Response + 4, // 7: grpc.DependencyServiceV2.UninstallDependencies:output_type -> grpc.Response + 4, // [4:8] is the sub-list for method output_type + 0, // [0:4] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_dependency_service_v2_proto_init() } +func file_services_dependency_service_v2_proto_init() { + if File_services_dependency_service_v2_proto != nil { + return + } + file_entity_dependencies_service_v2_request_proto_init() + file_entity_response_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_dependency_service_v2_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_dependency_service_v2_proto_goTypes, + DependencyIndexes: file_services_dependency_service_v2_proto_depIdxs, + }.Build() + File_services_dependency_service_v2_proto = out.File + file_services_dependency_service_v2_proto_rawDesc = nil + file_services_dependency_service_v2_proto_goTypes = nil + file_services_dependency_service_v2_proto_depIdxs = nil +} diff --git a/grpc/dependency_service_v2_grpc.pb.go b/grpc/dependency_service_v2_grpc.pb.go new file mode 100644 index 00000000..3d0a9eb9 --- /dev/null +++ b/grpc/dependency_service_v2_grpc.pb.go @@ -0,0 +1,312 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/dependency_service_v2.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// DependencyServiceV2Client is the client API for DependencyServiceV2 service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type DependencyServiceV2Client interface { + Connect(ctx context.Context, opts ...grpc.CallOption) (DependencyServiceV2_ConnectClient, error) + Sync(ctx context.Context, in *DependenciesServiceV2SyncRequest, opts ...grpc.CallOption) (*Response, error) + Install(ctx context.Context, opts ...grpc.CallOption) (DependencyServiceV2_InstallClient, error) + UninstallDependencies(ctx context.Context, opts ...grpc.CallOption) (DependencyServiceV2_UninstallDependenciesClient, error) +} + +type dependencyServiceV2Client struct { + cc grpc.ClientConnInterface +} + +func NewDependencyServiceV2Client(cc grpc.ClientConnInterface) DependencyServiceV2Client { + return &dependencyServiceV2Client{cc} +} + +func (c *dependencyServiceV2Client) Connect(ctx context.Context, opts ...grpc.CallOption) (DependencyServiceV2_ConnectClient, error) { + stream, err := c.cc.NewStream(ctx, &DependencyServiceV2_ServiceDesc.Streams[0], "/grpc.DependencyServiceV2/Connect", opts...) + if err != nil { + return nil, err + } + x := &dependencyServiceV2ConnectClient{stream} + return x, nil +} + +type DependencyServiceV2_ConnectClient interface { + Send(*DependenciesServiceV2ConnectRequest) error + CloseAndRecv() (*Response, error) + grpc.ClientStream +} + +type dependencyServiceV2ConnectClient struct { + grpc.ClientStream +} + +func (x *dependencyServiceV2ConnectClient) Send(m *DependenciesServiceV2ConnectRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *dependencyServiceV2ConnectClient) CloseAndRecv() (*Response, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *dependencyServiceV2Client) Sync(ctx context.Context, in *DependenciesServiceV2SyncRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.DependencyServiceV2/Sync", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *dependencyServiceV2Client) Install(ctx context.Context, opts ...grpc.CallOption) (DependencyServiceV2_InstallClient, error) { + stream, err := c.cc.NewStream(ctx, &DependencyServiceV2_ServiceDesc.Streams[1], "/grpc.DependencyServiceV2/Install", opts...) + if err != nil { + return nil, err + } + x := &dependencyServiceV2InstallClient{stream} + return x, nil +} + +type DependencyServiceV2_InstallClient interface { + Send(*DependenciesServiceV2InstallRequest) error + Recv() (*Response, error) + grpc.ClientStream +} + +type dependencyServiceV2InstallClient struct { + grpc.ClientStream +} + +func (x *dependencyServiceV2InstallClient) Send(m *DependenciesServiceV2InstallRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *dependencyServiceV2InstallClient) Recv() (*Response, error) { + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *dependencyServiceV2Client) UninstallDependencies(ctx context.Context, opts ...grpc.CallOption) (DependencyServiceV2_UninstallDependenciesClient, error) { + stream, err := c.cc.NewStream(ctx, &DependencyServiceV2_ServiceDesc.Streams[2], "/grpc.DependencyServiceV2/UninstallDependencies", opts...) + if err != nil { + return nil, err + } + x := &dependencyServiceV2UninstallDependenciesClient{stream} + return x, nil +} + +type DependencyServiceV2_UninstallDependenciesClient interface { + Send(*DependenciesServiceV2UninstallRequest) error + Recv() (*Response, error) + grpc.ClientStream +} + +type dependencyServiceV2UninstallDependenciesClient struct { + grpc.ClientStream +} + +func (x *dependencyServiceV2UninstallDependenciesClient) Send(m *DependenciesServiceV2UninstallRequest) error { + return x.ClientStream.SendMsg(m) +} + +func (x *dependencyServiceV2UninstallDependenciesClient) Recv() (*Response, error) { + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// DependencyServiceV2Server is the server API for DependencyServiceV2 service. +// All implementations must embed UnimplementedDependencyServiceV2Server +// for forward compatibility +type DependencyServiceV2Server interface { + Connect(DependencyServiceV2_ConnectServer) error + Sync(context.Context, *DependenciesServiceV2SyncRequest) (*Response, error) + Install(DependencyServiceV2_InstallServer) error + UninstallDependencies(DependencyServiceV2_UninstallDependenciesServer) error + mustEmbedUnimplementedDependencyServiceV2Server() +} + +// UnimplementedDependencyServiceV2Server must be embedded to have forward compatible implementations. +type UnimplementedDependencyServiceV2Server struct { +} + +func (UnimplementedDependencyServiceV2Server) Connect(DependencyServiceV2_ConnectServer) error { + return status.Errorf(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedDependencyServiceV2Server) Sync(context.Context, *DependenciesServiceV2SyncRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Sync not implemented") +} +func (UnimplementedDependencyServiceV2Server) Install(DependencyServiceV2_InstallServer) error { + return status.Errorf(codes.Unimplemented, "method Install not implemented") +} +func (UnimplementedDependencyServiceV2Server) UninstallDependencies(DependencyServiceV2_UninstallDependenciesServer) error { + return status.Errorf(codes.Unimplemented, "method UninstallDependencies not implemented") +} +func (UnimplementedDependencyServiceV2Server) mustEmbedUnimplementedDependencyServiceV2Server() {} + +// UnsafeDependencyServiceV2Server may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to DependencyServiceV2Server will +// result in compilation errors. +type UnsafeDependencyServiceV2Server interface { + mustEmbedUnimplementedDependencyServiceV2Server() +} + +func RegisterDependencyServiceV2Server(s grpc.ServiceRegistrar, srv DependencyServiceV2Server) { + s.RegisterService(&DependencyServiceV2_ServiceDesc, srv) +} + +func _DependencyServiceV2_Connect_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(DependencyServiceV2Server).Connect(&dependencyServiceV2ConnectServer{stream}) +} + +type DependencyServiceV2_ConnectServer interface { + SendAndClose(*Response) error + Recv() (*DependenciesServiceV2ConnectRequest, error) + grpc.ServerStream +} + +type dependencyServiceV2ConnectServer struct { + grpc.ServerStream +} + +func (x *dependencyServiceV2ConnectServer) SendAndClose(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *dependencyServiceV2ConnectServer) Recv() (*DependenciesServiceV2ConnectRequest, error) { + m := new(DependenciesServiceV2ConnectRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _DependencyServiceV2_Sync_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DependenciesServiceV2SyncRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DependencyServiceV2Server).Sync(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.DependencyServiceV2/Sync", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DependencyServiceV2Server).Sync(ctx, req.(*DependenciesServiceV2SyncRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DependencyServiceV2_Install_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(DependencyServiceV2Server).Install(&dependencyServiceV2InstallServer{stream}) +} + +type DependencyServiceV2_InstallServer interface { + Send(*Response) error + Recv() (*DependenciesServiceV2InstallRequest, error) + grpc.ServerStream +} + +type dependencyServiceV2InstallServer struct { + grpc.ServerStream +} + +func (x *dependencyServiceV2InstallServer) Send(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *dependencyServiceV2InstallServer) Recv() (*DependenciesServiceV2InstallRequest, error) { + m := new(DependenciesServiceV2InstallRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _DependencyServiceV2_UninstallDependencies_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(DependencyServiceV2Server).UninstallDependencies(&dependencyServiceV2UninstallDependenciesServer{stream}) +} + +type DependencyServiceV2_UninstallDependenciesServer interface { + Send(*Response) error + Recv() (*DependenciesServiceV2UninstallRequest, error) + grpc.ServerStream +} + +type dependencyServiceV2UninstallDependenciesServer struct { + grpc.ServerStream +} + +func (x *dependencyServiceV2UninstallDependenciesServer) Send(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *dependencyServiceV2UninstallDependenciesServer) Recv() (*DependenciesServiceV2UninstallRequest, error) { + m := new(DependenciesServiceV2UninstallRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// DependencyServiceV2_ServiceDesc is the grpc.ServiceDesc for DependencyServiceV2 service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var DependencyServiceV2_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.DependencyServiceV2", + HandlerType: (*DependencyServiceV2Server)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Sync", + Handler: _DependencyServiceV2_Sync_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Connect", + Handler: _DependencyServiceV2_Connect_Handler, + ClientStreams: true, + }, + { + StreamName: "Install", + Handler: _DependencyServiceV2_Install_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "UninstallDependencies", + Handler: _DependencyServiceV2_UninstallDependencies_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "services/dependency_service_v2.proto", +} diff --git a/grpc/go.mod b/grpc/go.mod new file mode 100644 index 00000000..bbc0968c --- /dev/null +++ b/grpc/go.mod @@ -0,0 +1,16 @@ +module github.com/crawlab-team/crawlab/grpc + +go 1.22 + +require ( + github.com/golang/protobuf v1.5.4 + google.golang.org/grpc v1.64.0 + google.golang.org/protobuf v1.34.1 +) + +require ( + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect +) diff --git a/grpc/go.sum b/grpc/go.sum new file mode 100644 index 00000000..d41d2dc2 --- /dev/null +++ b/grpc/go.sum @@ -0,0 +1,16 @@ +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/grpc/message_service.pb.go b/grpc/message_service.pb.go new file mode 100644 index 00000000..fca04128 --- /dev/null +++ b/grpc/message_service.pb.go @@ -0,0 +1,79 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/message_service.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_message_service_proto protoreflect.FileDescriptor + +var file_services_message_service_proto_rawDesc = []byte{ + 0x0a, 0x1e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x32, 0x4b, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x12, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, + 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var file_services_message_service_proto_goTypes = []interface{}{ + (*StreamMessage)(nil), // 0: grpc.StreamMessage +} +var file_services_message_service_proto_depIdxs = []int32{ + 0, // 0: grpc.MessageService.Connect:input_type -> grpc.StreamMessage + 0, // 1: grpc.MessageService.Connect:output_type -> grpc.StreamMessage + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_message_service_proto_init() } +func file_services_message_service_proto_init() { + if File_services_message_service_proto != nil { + return + } + file_entity_stream_message_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_message_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_message_service_proto_goTypes, + DependencyIndexes: file_services_message_service_proto_depIdxs, + }.Build() + File_services_message_service_proto = out.File + file_services_message_service_proto_rawDesc = nil + file_services_message_service_proto_goTypes = nil + file_services_message_service_proto_depIdxs = nil +} diff --git a/grpc/message_service_grpc.pb.go b/grpc/message_service_grpc.pb.go new file mode 100644 index 00000000..c1cc17b0 --- /dev/null +++ b/grpc/message_service_grpc.pb.go @@ -0,0 +1,137 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/message_service.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// MessageServiceClient is the client API for MessageService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type MessageServiceClient interface { + Connect(ctx context.Context, opts ...grpc.CallOption) (MessageService_ConnectClient, error) +} + +type messageServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewMessageServiceClient(cc grpc.ClientConnInterface) MessageServiceClient { + return &messageServiceClient{cc} +} + +func (c *messageServiceClient) Connect(ctx context.Context, opts ...grpc.CallOption) (MessageService_ConnectClient, error) { + stream, err := c.cc.NewStream(ctx, &MessageService_ServiceDesc.Streams[0], "/grpc.MessageService/Connect", opts...) + if err != nil { + return nil, err + } + x := &messageServiceConnectClient{stream} + return x, nil +} + +type MessageService_ConnectClient interface { + Send(*StreamMessage) error + Recv() (*StreamMessage, error) + grpc.ClientStream +} + +type messageServiceConnectClient struct { + grpc.ClientStream +} + +func (x *messageServiceConnectClient) Send(m *StreamMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *messageServiceConnectClient) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// MessageServiceServer is the server API for MessageService service. +// All implementations must embed UnimplementedMessageServiceServer +// for forward compatibility +type MessageServiceServer interface { + Connect(MessageService_ConnectServer) error + mustEmbedUnimplementedMessageServiceServer() +} + +// UnimplementedMessageServiceServer must be embedded to have forward compatible implementations. +type UnimplementedMessageServiceServer struct { +} + +func (UnimplementedMessageServiceServer) Connect(MessageService_ConnectServer) error { + return status.Errorf(codes.Unimplemented, "method Connect not implemented") +} +func (UnimplementedMessageServiceServer) mustEmbedUnimplementedMessageServiceServer() {} + +// UnsafeMessageServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MessageServiceServer will +// result in compilation errors. +type UnsafeMessageServiceServer interface { + mustEmbedUnimplementedMessageServiceServer() +} + +func RegisterMessageServiceServer(s grpc.ServiceRegistrar, srv MessageServiceServer) { + s.RegisterService(&MessageService_ServiceDesc, srv) +} + +func _MessageService_Connect_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MessageServiceServer).Connect(&messageServiceConnectServer{stream}) +} + +type MessageService_ConnectServer interface { + Send(*StreamMessage) error + Recv() (*StreamMessage, error) + grpc.ServerStream +} + +type messageServiceConnectServer struct { + grpc.ServerStream +} + +func (x *messageServiceConnectServer) Send(m *StreamMessage) error { + return x.ServerStream.SendMsg(m) +} + +func (x *messageServiceConnectServer) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// MessageService_ServiceDesc is the grpc.ServiceDesc for MessageService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var MessageService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.MessageService", + HandlerType: (*MessageServiceServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "Connect", + Handler: _MessageService_Connect_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "services/message_service.proto", +} diff --git a/grpc/model_base_service.pb.go b/grpc/model_base_service.pb.go new file mode 100644 index 00000000..0f9af695 --- /dev/null +++ b/grpc/model_base_service.pb.go @@ -0,0 +1,134 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/model_base_service.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_model_base_service_proto protoreflect.FileDescriptor + +var file_services_model_base_service_proto_rawDesc = []byte{ + 0x0a, 0x21, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x14, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, + 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xac, 0x04, 0x0a, 0x10, 0x4d, 0x6f, 0x64, 0x65, 0x6c, + 0x42, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2a, 0x0a, 0x07, 0x47, + 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x26, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x0d, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x2a, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x0a, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x79, 0x49, 0x64, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x29, 0x0a, 0x06, 0x44, 0x65, + 0x6c, 0x65, 0x74, 0x65, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x32, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x42, 0x79, 0x49, 0x64, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x29, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x2c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x44, 0x6f, 0x63, 0x12, + 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x29, 0x0a, 0x06, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x28, 0x0a, 0x05, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_services_model_base_service_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: grpc.Request + (*Response)(nil), // 1: grpc.Response +} +var file_services_model_base_service_proto_depIdxs = []int32{ + 0, // 0: grpc.ModelBaseService.GetById:input_type -> grpc.Request + 0, // 1: grpc.ModelBaseService.Get:input_type -> grpc.Request + 0, // 2: grpc.ModelBaseService.GetList:input_type -> grpc.Request + 0, // 3: grpc.ModelBaseService.DeleteById:input_type -> grpc.Request + 0, // 4: grpc.ModelBaseService.Delete:input_type -> grpc.Request + 0, // 5: grpc.ModelBaseService.DeleteList:input_type -> grpc.Request + 0, // 6: grpc.ModelBaseService.ForceDeleteList:input_type -> grpc.Request + 0, // 7: grpc.ModelBaseService.UpdateById:input_type -> grpc.Request + 0, // 8: grpc.ModelBaseService.Update:input_type -> grpc.Request + 0, // 9: grpc.ModelBaseService.UpdateDoc:input_type -> grpc.Request + 0, // 10: grpc.ModelBaseService.Insert:input_type -> grpc.Request + 0, // 11: grpc.ModelBaseService.Count:input_type -> grpc.Request + 1, // 12: grpc.ModelBaseService.GetById:output_type -> grpc.Response + 1, // 13: grpc.ModelBaseService.Get:output_type -> grpc.Response + 1, // 14: grpc.ModelBaseService.GetList:output_type -> grpc.Response + 1, // 15: grpc.ModelBaseService.DeleteById:output_type -> grpc.Response + 1, // 16: grpc.ModelBaseService.Delete:output_type -> grpc.Response + 1, // 17: grpc.ModelBaseService.DeleteList:output_type -> grpc.Response + 1, // 18: grpc.ModelBaseService.ForceDeleteList:output_type -> grpc.Response + 1, // 19: grpc.ModelBaseService.UpdateById:output_type -> grpc.Response + 1, // 20: grpc.ModelBaseService.Update:output_type -> grpc.Response + 1, // 21: grpc.ModelBaseService.UpdateDoc:output_type -> grpc.Response + 1, // 22: grpc.ModelBaseService.Insert:output_type -> grpc.Response + 1, // 23: grpc.ModelBaseService.Count:output_type -> grpc.Response + 12, // [12:24] is the sub-list for method output_type + 0, // [0:12] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_model_base_service_proto_init() } +func file_services_model_base_service_proto_init() { + if File_services_model_base_service_proto != nil { + return + } + file_entity_request_proto_init() + file_entity_response_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_model_base_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_model_base_service_proto_goTypes, + DependencyIndexes: file_services_model_base_service_proto_depIdxs, + }.Build() + File_services_model_base_service_proto = out.File + file_services_model_base_service_proto_rawDesc = nil + file_services_model_base_service_proto_goTypes = nil + file_services_model_base_service_proto_depIdxs = nil +} diff --git a/grpc/model_base_service_grpc.pb.go b/grpc/model_base_service_grpc.pb.go new file mode 100644 index 00000000..465355ec --- /dev/null +++ b/grpc/model_base_service_grpc.pb.go @@ -0,0 +1,501 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/model_base_service.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ModelBaseServiceClient is the client API for ModelBaseService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ModelBaseServiceClient interface { + GetById(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Get(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + GetList(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + DeleteById(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Delete(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + DeleteList(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + ForceDeleteList(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + UpdateById(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Update(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + UpdateDoc(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Insert(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Count(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type modelBaseServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewModelBaseServiceClient(cc grpc.ClientConnInterface) ModelBaseServiceClient { + return &modelBaseServiceClient{cc} +} + +func (c *modelBaseServiceClient) GetById(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/GetById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) Get(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/Get", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) GetList(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/GetList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) DeleteById(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/DeleteById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) Delete(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/Delete", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) DeleteList(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/DeleteList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) ForceDeleteList(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/ForceDeleteList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) UpdateById(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/UpdateById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) Update(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/Update", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) UpdateDoc(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/UpdateDoc", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) Insert(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/Insert", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceClient) Count(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseService/Count", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ModelBaseServiceServer is the server API for ModelBaseService service. +// All implementations must embed UnimplementedModelBaseServiceServer +// for forward compatibility +type ModelBaseServiceServer interface { + GetById(context.Context, *Request) (*Response, error) + Get(context.Context, *Request) (*Response, error) + GetList(context.Context, *Request) (*Response, error) + DeleteById(context.Context, *Request) (*Response, error) + Delete(context.Context, *Request) (*Response, error) + DeleteList(context.Context, *Request) (*Response, error) + ForceDeleteList(context.Context, *Request) (*Response, error) + UpdateById(context.Context, *Request) (*Response, error) + Update(context.Context, *Request) (*Response, error) + UpdateDoc(context.Context, *Request) (*Response, error) + Insert(context.Context, *Request) (*Response, error) + Count(context.Context, *Request) (*Response, error) + mustEmbedUnimplementedModelBaseServiceServer() +} + +// UnimplementedModelBaseServiceServer must be embedded to have forward compatible implementations. +type UnimplementedModelBaseServiceServer struct { +} + +func (UnimplementedModelBaseServiceServer) GetById(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetById not implemented") +} +func (UnimplementedModelBaseServiceServer) Get(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Get not implemented") +} +func (UnimplementedModelBaseServiceServer) GetList(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetList not implemented") +} +func (UnimplementedModelBaseServiceServer) DeleteById(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteById not implemented") +} +func (UnimplementedModelBaseServiceServer) Delete(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedModelBaseServiceServer) DeleteList(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteList not implemented") +} +func (UnimplementedModelBaseServiceServer) ForceDeleteList(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method ForceDeleteList not implemented") +} +func (UnimplementedModelBaseServiceServer) UpdateById(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateById not implemented") +} +func (UnimplementedModelBaseServiceServer) Update(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") +} +func (UnimplementedModelBaseServiceServer) UpdateDoc(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateDoc not implemented") +} +func (UnimplementedModelBaseServiceServer) Insert(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Insert not implemented") +} +func (UnimplementedModelBaseServiceServer) Count(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Count not implemented") +} +func (UnimplementedModelBaseServiceServer) mustEmbedUnimplementedModelBaseServiceServer() {} + +// UnsafeModelBaseServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ModelBaseServiceServer will +// result in compilation errors. +type UnsafeModelBaseServiceServer interface { + mustEmbedUnimplementedModelBaseServiceServer() +} + +func RegisterModelBaseServiceServer(s grpc.ServiceRegistrar, srv ModelBaseServiceServer) { + s.RegisterService(&ModelBaseService_ServiceDesc, srv) +} + +func _ModelBaseService_GetById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).GetById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/GetById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).GetById(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).Get(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_GetList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).GetList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/GetList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).GetList(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_DeleteById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).DeleteById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/DeleteById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).DeleteById(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/Delete", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).Delete(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_DeleteList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).DeleteList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/DeleteList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).DeleteList(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_ForceDeleteList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).ForceDeleteList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/ForceDeleteList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).ForceDeleteList(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_UpdateById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).UpdateById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/UpdateById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).UpdateById(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).Update(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/Update", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).Update(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_UpdateDoc_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).UpdateDoc(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/UpdateDoc", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).UpdateDoc(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_Insert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).Insert(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/Insert", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).Insert(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseService_Count_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceServer).Count(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseService/Count", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceServer).Count(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +// ModelBaseService_ServiceDesc is the grpc.ServiceDesc for ModelBaseService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ModelBaseService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.ModelBaseService", + HandlerType: (*ModelBaseServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetById", + Handler: _ModelBaseService_GetById_Handler, + }, + { + MethodName: "Get", + Handler: _ModelBaseService_Get_Handler, + }, + { + MethodName: "GetList", + Handler: _ModelBaseService_GetList_Handler, + }, + { + MethodName: "DeleteById", + Handler: _ModelBaseService_DeleteById_Handler, + }, + { + MethodName: "Delete", + Handler: _ModelBaseService_Delete_Handler, + }, + { + MethodName: "DeleteList", + Handler: _ModelBaseService_DeleteList_Handler, + }, + { + MethodName: "ForceDeleteList", + Handler: _ModelBaseService_ForceDeleteList_Handler, + }, + { + MethodName: "UpdateById", + Handler: _ModelBaseService_UpdateById_Handler, + }, + { + MethodName: "Update", + Handler: _ModelBaseService_Update_Handler, + }, + { + MethodName: "UpdateDoc", + Handler: _ModelBaseService_UpdateDoc_Handler, + }, + { + MethodName: "Insert", + Handler: _ModelBaseService_Insert_Handler, + }, + { + MethodName: "Count", + Handler: _ModelBaseService_Count_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "services/model_base_service.proto", +} diff --git a/grpc/model_base_service_v2.pb.go b/grpc/model_base_service_v2.pb.go new file mode 100644 index 00000000..22377ca9 --- /dev/null +++ b/grpc/model_base_service_v2.pb.go @@ -0,0 +1,179 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/model_base_service_v2.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_model_base_service_v2_proto protoreflect.FileDescriptor + +var file_services_model_base_service_v2_proto_rawDesc = []byte{ + 0x0a, 0x24, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x32, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x25, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x5f, 0x76, 0x32, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xd4, 0x07, 0x0a, 0x12, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x42, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, + 0x32, 0x12, 0x3f, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x12, 0x22, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x56, 0x32, 0x47, 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x06, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x65, 0x12, 0x21, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x56, 0x32, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x3f, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x22, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x56, 0x32, 0x47, 0x65, 0x74, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x79, 0x49, 0x64, + 0x12, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x79, 0x49, 0x64, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x09, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4f, 0x6e, 0x65, 0x12, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, + 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x4f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, + 0x0a, 0x0a, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x25, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x56, 0x32, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, + 0x79, 0x49, 0x64, 0x12, 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, + 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x09, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x65, 0x12, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x79, 0x12, + 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x56, 0x32, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x42, 0x79, 0x49, 0x64, 0x12, 0x26, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x52, 0x65, 0x70, + 0x6c, 0x61, 0x63, 0x65, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x45, 0x0a, 0x0a, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x6e, 0x65, 0x12, + 0x25, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x56, 0x32, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x6e, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x65, + 0x72, 0x74, 0x4f, 0x6e, 0x65, 0x12, 0x24, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, + 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x49, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x4f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a, + 0x0a, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x61, 0x6e, 0x79, 0x12, 0x25, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, + 0x32, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x05, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x20, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x56, 0x32, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var file_services_model_base_service_v2_proto_goTypes = []interface{}{ + (*ModelServiceV2GetByIdRequest)(nil), // 0: grpc.ModelServiceV2GetByIdRequest + (*ModelServiceV2GetOneRequest)(nil), // 1: grpc.ModelServiceV2GetOneRequest + (*ModelServiceV2GetManyRequest)(nil), // 2: grpc.ModelServiceV2GetManyRequest + (*ModelServiceV2DeleteByIdRequest)(nil), // 3: grpc.ModelServiceV2DeleteByIdRequest + (*ModelServiceV2DeleteOneRequest)(nil), // 4: grpc.ModelServiceV2DeleteOneRequest + (*ModelServiceV2DeleteManyRequest)(nil), // 5: grpc.ModelServiceV2DeleteManyRequest + (*ModelServiceV2UpdateByIdRequest)(nil), // 6: grpc.ModelServiceV2UpdateByIdRequest + (*ModelServiceV2UpdateOneRequest)(nil), // 7: grpc.ModelServiceV2UpdateOneRequest + (*ModelServiceV2UpdateManyRequest)(nil), // 8: grpc.ModelServiceV2UpdateManyRequest + (*ModelServiceV2ReplaceByIdRequest)(nil), // 9: grpc.ModelServiceV2ReplaceByIdRequest + (*ModelServiceV2ReplaceOneRequest)(nil), // 10: grpc.ModelServiceV2ReplaceOneRequest + (*ModelServiceV2InsertOneRequest)(nil), // 11: grpc.ModelServiceV2InsertOneRequest + (*ModelServiceV2InsertManyRequest)(nil), // 12: grpc.ModelServiceV2InsertManyRequest + (*ModelServiceV2CountRequest)(nil), // 13: grpc.ModelServiceV2CountRequest + (*Response)(nil), // 14: grpc.Response +} +var file_services_model_base_service_v2_proto_depIdxs = []int32{ + 0, // 0: grpc.ModelBaseServiceV2.GetById:input_type -> grpc.ModelServiceV2GetByIdRequest + 1, // 1: grpc.ModelBaseServiceV2.GetOne:input_type -> grpc.ModelServiceV2GetOneRequest + 2, // 2: grpc.ModelBaseServiceV2.GetMany:input_type -> grpc.ModelServiceV2GetManyRequest + 3, // 3: grpc.ModelBaseServiceV2.DeleteById:input_type -> grpc.ModelServiceV2DeleteByIdRequest + 4, // 4: grpc.ModelBaseServiceV2.DeleteOne:input_type -> grpc.ModelServiceV2DeleteOneRequest + 5, // 5: grpc.ModelBaseServiceV2.DeleteMany:input_type -> grpc.ModelServiceV2DeleteManyRequest + 6, // 6: grpc.ModelBaseServiceV2.UpdateById:input_type -> grpc.ModelServiceV2UpdateByIdRequest + 7, // 7: grpc.ModelBaseServiceV2.UpdateOne:input_type -> grpc.ModelServiceV2UpdateOneRequest + 8, // 8: grpc.ModelBaseServiceV2.UpdateMany:input_type -> grpc.ModelServiceV2UpdateManyRequest + 9, // 9: grpc.ModelBaseServiceV2.ReplaceById:input_type -> grpc.ModelServiceV2ReplaceByIdRequest + 10, // 10: grpc.ModelBaseServiceV2.ReplaceOne:input_type -> grpc.ModelServiceV2ReplaceOneRequest + 11, // 11: grpc.ModelBaseServiceV2.InsertOne:input_type -> grpc.ModelServiceV2InsertOneRequest + 12, // 12: grpc.ModelBaseServiceV2.InsertMany:input_type -> grpc.ModelServiceV2InsertManyRequest + 13, // 13: grpc.ModelBaseServiceV2.Count:input_type -> grpc.ModelServiceV2CountRequest + 14, // 14: grpc.ModelBaseServiceV2.GetById:output_type -> grpc.Response + 14, // 15: grpc.ModelBaseServiceV2.GetOne:output_type -> grpc.Response + 14, // 16: grpc.ModelBaseServiceV2.GetMany:output_type -> grpc.Response + 14, // 17: grpc.ModelBaseServiceV2.DeleteById:output_type -> grpc.Response + 14, // 18: grpc.ModelBaseServiceV2.DeleteOne:output_type -> grpc.Response + 14, // 19: grpc.ModelBaseServiceV2.DeleteMany:output_type -> grpc.Response + 14, // 20: grpc.ModelBaseServiceV2.UpdateById:output_type -> grpc.Response + 14, // 21: grpc.ModelBaseServiceV2.UpdateOne:output_type -> grpc.Response + 14, // 22: grpc.ModelBaseServiceV2.UpdateMany:output_type -> grpc.Response + 14, // 23: grpc.ModelBaseServiceV2.ReplaceById:output_type -> grpc.Response + 14, // 24: grpc.ModelBaseServiceV2.ReplaceOne:output_type -> grpc.Response + 14, // 25: grpc.ModelBaseServiceV2.InsertOne:output_type -> grpc.Response + 14, // 26: grpc.ModelBaseServiceV2.InsertMany:output_type -> grpc.Response + 14, // 27: grpc.ModelBaseServiceV2.Count:output_type -> grpc.Response + 14, // [14:28] is the sub-list for method output_type + 0, // [0:14] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_model_base_service_v2_proto_init() } +func file_services_model_base_service_v2_proto_init() { + if File_services_model_base_service_v2_proto != nil { + return + } + file_entity_model_service_v2_request_proto_init() + file_entity_response_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_model_base_service_v2_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_model_base_service_v2_proto_goTypes, + DependencyIndexes: file_services_model_base_service_v2_proto_depIdxs, + }.Build() + File_services_model_base_service_v2_proto = out.File + file_services_model_base_service_v2_proto_rawDesc = nil + file_services_model_base_service_v2_proto_goTypes = nil + file_services_model_base_service_v2_proto_depIdxs = nil +} diff --git a/grpc/model_base_service_v2_grpc.pb.go b/grpc/model_base_service_v2_grpc.pb.go new file mode 100644 index 00000000..6f5fac73 --- /dev/null +++ b/grpc/model_base_service_v2_grpc.pb.go @@ -0,0 +1,573 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/model_base_service_v2.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ModelBaseServiceV2Client is the client API for ModelBaseServiceV2 service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ModelBaseServiceV2Client interface { + GetById(ctx context.Context, in *ModelServiceV2GetByIdRequest, opts ...grpc.CallOption) (*Response, error) + GetOne(ctx context.Context, in *ModelServiceV2GetOneRequest, opts ...grpc.CallOption) (*Response, error) + GetMany(ctx context.Context, in *ModelServiceV2GetManyRequest, opts ...grpc.CallOption) (*Response, error) + DeleteById(ctx context.Context, in *ModelServiceV2DeleteByIdRequest, opts ...grpc.CallOption) (*Response, error) + DeleteOne(ctx context.Context, in *ModelServiceV2DeleteOneRequest, opts ...grpc.CallOption) (*Response, error) + DeleteMany(ctx context.Context, in *ModelServiceV2DeleteManyRequest, opts ...grpc.CallOption) (*Response, error) + UpdateById(ctx context.Context, in *ModelServiceV2UpdateByIdRequest, opts ...grpc.CallOption) (*Response, error) + UpdateOne(ctx context.Context, in *ModelServiceV2UpdateOneRequest, opts ...grpc.CallOption) (*Response, error) + UpdateMany(ctx context.Context, in *ModelServiceV2UpdateManyRequest, opts ...grpc.CallOption) (*Response, error) + ReplaceById(ctx context.Context, in *ModelServiceV2ReplaceByIdRequest, opts ...grpc.CallOption) (*Response, error) + ReplaceOne(ctx context.Context, in *ModelServiceV2ReplaceOneRequest, opts ...grpc.CallOption) (*Response, error) + InsertOne(ctx context.Context, in *ModelServiceV2InsertOneRequest, opts ...grpc.CallOption) (*Response, error) + InsertMany(ctx context.Context, in *ModelServiceV2InsertManyRequest, opts ...grpc.CallOption) (*Response, error) + Count(ctx context.Context, in *ModelServiceV2CountRequest, opts ...grpc.CallOption) (*Response, error) +} + +type modelBaseServiceV2Client struct { + cc grpc.ClientConnInterface +} + +func NewModelBaseServiceV2Client(cc grpc.ClientConnInterface) ModelBaseServiceV2Client { + return &modelBaseServiceV2Client{cc} +} + +func (c *modelBaseServiceV2Client) GetById(ctx context.Context, in *ModelServiceV2GetByIdRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/GetById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) GetOne(ctx context.Context, in *ModelServiceV2GetOneRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/GetOne", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) GetMany(ctx context.Context, in *ModelServiceV2GetManyRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/GetMany", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) DeleteById(ctx context.Context, in *ModelServiceV2DeleteByIdRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/DeleteById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) DeleteOne(ctx context.Context, in *ModelServiceV2DeleteOneRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/DeleteOne", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) DeleteMany(ctx context.Context, in *ModelServiceV2DeleteManyRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/DeleteMany", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) UpdateById(ctx context.Context, in *ModelServiceV2UpdateByIdRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/UpdateById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) UpdateOne(ctx context.Context, in *ModelServiceV2UpdateOneRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/UpdateOne", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) UpdateMany(ctx context.Context, in *ModelServiceV2UpdateManyRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/UpdateMany", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) ReplaceById(ctx context.Context, in *ModelServiceV2ReplaceByIdRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/ReplaceById", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) ReplaceOne(ctx context.Context, in *ModelServiceV2ReplaceOneRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/ReplaceOne", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) InsertOne(ctx context.Context, in *ModelServiceV2InsertOneRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/InsertOne", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) InsertMany(ctx context.Context, in *ModelServiceV2InsertManyRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/InsertMany", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelBaseServiceV2Client) Count(ctx context.Context, in *ModelServiceV2CountRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelBaseServiceV2/Count", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ModelBaseServiceV2Server is the server API for ModelBaseServiceV2 service. +// All implementations must embed UnimplementedModelBaseServiceV2Server +// for forward compatibility +type ModelBaseServiceV2Server interface { + GetById(context.Context, *ModelServiceV2GetByIdRequest) (*Response, error) + GetOne(context.Context, *ModelServiceV2GetOneRequest) (*Response, error) + GetMany(context.Context, *ModelServiceV2GetManyRequest) (*Response, error) + DeleteById(context.Context, *ModelServiceV2DeleteByIdRequest) (*Response, error) + DeleteOne(context.Context, *ModelServiceV2DeleteOneRequest) (*Response, error) + DeleteMany(context.Context, *ModelServiceV2DeleteManyRequest) (*Response, error) + UpdateById(context.Context, *ModelServiceV2UpdateByIdRequest) (*Response, error) + UpdateOne(context.Context, *ModelServiceV2UpdateOneRequest) (*Response, error) + UpdateMany(context.Context, *ModelServiceV2UpdateManyRequest) (*Response, error) + ReplaceById(context.Context, *ModelServiceV2ReplaceByIdRequest) (*Response, error) + ReplaceOne(context.Context, *ModelServiceV2ReplaceOneRequest) (*Response, error) + InsertOne(context.Context, *ModelServiceV2InsertOneRequest) (*Response, error) + InsertMany(context.Context, *ModelServiceV2InsertManyRequest) (*Response, error) + Count(context.Context, *ModelServiceV2CountRequest) (*Response, error) + mustEmbedUnimplementedModelBaseServiceV2Server() +} + +// UnimplementedModelBaseServiceV2Server must be embedded to have forward compatible implementations. +type UnimplementedModelBaseServiceV2Server struct { +} + +func (UnimplementedModelBaseServiceV2Server) GetById(context.Context, *ModelServiceV2GetByIdRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetById not implemented") +} +func (UnimplementedModelBaseServiceV2Server) GetOne(context.Context, *ModelServiceV2GetOneRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetOne not implemented") +} +func (UnimplementedModelBaseServiceV2Server) GetMany(context.Context, *ModelServiceV2GetManyRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetMany not implemented") +} +func (UnimplementedModelBaseServiceV2Server) DeleteById(context.Context, *ModelServiceV2DeleteByIdRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteById not implemented") +} +func (UnimplementedModelBaseServiceV2Server) DeleteOne(context.Context, *ModelServiceV2DeleteOneRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteOne not implemented") +} +func (UnimplementedModelBaseServiceV2Server) DeleteMany(context.Context, *ModelServiceV2DeleteManyRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteMany not implemented") +} +func (UnimplementedModelBaseServiceV2Server) UpdateById(context.Context, *ModelServiceV2UpdateByIdRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateById not implemented") +} +func (UnimplementedModelBaseServiceV2Server) UpdateOne(context.Context, *ModelServiceV2UpdateOneRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateOne not implemented") +} +func (UnimplementedModelBaseServiceV2Server) UpdateMany(context.Context, *ModelServiceV2UpdateManyRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateMany not implemented") +} +func (UnimplementedModelBaseServiceV2Server) ReplaceById(context.Context, *ModelServiceV2ReplaceByIdRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReplaceById not implemented") +} +func (UnimplementedModelBaseServiceV2Server) ReplaceOne(context.Context, *ModelServiceV2ReplaceOneRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method ReplaceOne not implemented") +} +func (UnimplementedModelBaseServiceV2Server) InsertOne(context.Context, *ModelServiceV2InsertOneRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method InsertOne not implemented") +} +func (UnimplementedModelBaseServiceV2Server) InsertMany(context.Context, *ModelServiceV2InsertManyRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method InsertMany not implemented") +} +func (UnimplementedModelBaseServiceV2Server) Count(context.Context, *ModelServiceV2CountRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Count not implemented") +} +func (UnimplementedModelBaseServiceV2Server) mustEmbedUnimplementedModelBaseServiceV2Server() {} + +// UnsafeModelBaseServiceV2Server may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ModelBaseServiceV2Server will +// result in compilation errors. +type UnsafeModelBaseServiceV2Server interface { + mustEmbedUnimplementedModelBaseServiceV2Server() +} + +func RegisterModelBaseServiceV2Server(s grpc.ServiceRegistrar, srv ModelBaseServiceV2Server) { + s.RegisterService(&ModelBaseServiceV2_ServiceDesc, srv) +} + +func _ModelBaseServiceV2_GetById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2GetByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).GetById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/GetById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).GetById(ctx, req.(*ModelServiceV2GetByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_GetOne_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2GetOneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).GetOne(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/GetOne", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).GetOne(ctx, req.(*ModelServiceV2GetOneRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_GetMany_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2GetManyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).GetMany(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/GetMany", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).GetMany(ctx, req.(*ModelServiceV2GetManyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_DeleteById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2DeleteByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).DeleteById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/DeleteById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).DeleteById(ctx, req.(*ModelServiceV2DeleteByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_DeleteOne_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2DeleteOneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).DeleteOne(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/DeleteOne", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).DeleteOne(ctx, req.(*ModelServiceV2DeleteOneRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_DeleteMany_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2DeleteManyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).DeleteMany(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/DeleteMany", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).DeleteMany(ctx, req.(*ModelServiceV2DeleteManyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_UpdateById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2UpdateByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).UpdateById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/UpdateById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).UpdateById(ctx, req.(*ModelServiceV2UpdateByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_UpdateOne_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2UpdateOneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).UpdateOne(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/UpdateOne", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).UpdateOne(ctx, req.(*ModelServiceV2UpdateOneRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_UpdateMany_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2UpdateManyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).UpdateMany(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/UpdateMany", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).UpdateMany(ctx, req.(*ModelServiceV2UpdateManyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_ReplaceById_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2ReplaceByIdRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).ReplaceById(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/ReplaceById", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).ReplaceById(ctx, req.(*ModelServiceV2ReplaceByIdRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_ReplaceOne_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2ReplaceOneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).ReplaceOne(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/ReplaceOne", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).ReplaceOne(ctx, req.(*ModelServiceV2ReplaceOneRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_InsertOne_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2InsertOneRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).InsertOne(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/InsertOne", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).InsertOne(ctx, req.(*ModelServiceV2InsertOneRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_InsertMany_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2InsertManyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).InsertMany(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/InsertMany", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).InsertMany(ctx, req.(*ModelServiceV2InsertManyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelBaseServiceV2_Count_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ModelServiceV2CountRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelBaseServiceV2Server).Count(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelBaseServiceV2/Count", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelBaseServiceV2Server).Count(ctx, req.(*ModelServiceV2CountRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ModelBaseServiceV2_ServiceDesc is the grpc.ServiceDesc for ModelBaseServiceV2 service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ModelBaseServiceV2_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.ModelBaseServiceV2", + HandlerType: (*ModelBaseServiceV2Server)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetById", + Handler: _ModelBaseServiceV2_GetById_Handler, + }, + { + MethodName: "GetOne", + Handler: _ModelBaseServiceV2_GetOne_Handler, + }, + { + MethodName: "GetMany", + Handler: _ModelBaseServiceV2_GetMany_Handler, + }, + { + MethodName: "DeleteById", + Handler: _ModelBaseServiceV2_DeleteById_Handler, + }, + { + MethodName: "DeleteOne", + Handler: _ModelBaseServiceV2_DeleteOne_Handler, + }, + { + MethodName: "DeleteMany", + Handler: _ModelBaseServiceV2_DeleteMany_Handler, + }, + { + MethodName: "UpdateById", + Handler: _ModelBaseServiceV2_UpdateById_Handler, + }, + { + MethodName: "UpdateOne", + Handler: _ModelBaseServiceV2_UpdateOne_Handler, + }, + { + MethodName: "UpdateMany", + Handler: _ModelBaseServiceV2_UpdateMany_Handler, + }, + { + MethodName: "ReplaceById", + Handler: _ModelBaseServiceV2_ReplaceById_Handler, + }, + { + MethodName: "ReplaceOne", + Handler: _ModelBaseServiceV2_ReplaceOne_Handler, + }, + { + MethodName: "InsertOne", + Handler: _ModelBaseServiceV2_InsertOne_Handler, + }, + { + MethodName: "InsertMany", + Handler: _ModelBaseServiceV2_InsertMany_Handler, + }, + { + MethodName: "Count", + Handler: _ModelBaseServiceV2_Count_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "services/model_base_service_v2.proto", +} diff --git a/grpc/model_delegate.pb.go b/grpc/model_delegate.pb.go new file mode 100644 index 00000000..76dfa489 --- /dev/null +++ b/grpc/model_delegate.pb.go @@ -0,0 +1,80 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/model_delegate.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_model_delegate_proto protoreflect.FileDescriptor + +var file_services_model_delegate_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x14, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x32, 0x36, 0x0a, 0x0d, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x44, 0x65, 0x6c, 0x65, 0x67, + 0x61, 0x74, 0x65, 0x12, 0x25, 0x0a, 0x02, 0x44, 0x6f, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, + 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_services_model_delegate_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: grpc.Request + (*Response)(nil), // 1: grpc.Response +} +var file_services_model_delegate_proto_depIdxs = []int32{ + 0, // 0: grpc.ModelDelegate.Do:input_type -> grpc.Request + 1, // 1: grpc.ModelDelegate.Do:output_type -> grpc.Response + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_model_delegate_proto_init() } +func file_services_model_delegate_proto_init() { + if File_services_model_delegate_proto != nil { + return + } + file_entity_request_proto_init() + file_entity_response_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_model_delegate_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_model_delegate_proto_goTypes, + DependencyIndexes: file_services_model_delegate_proto_depIdxs, + }.Build() + File_services_model_delegate_proto = out.File + file_services_model_delegate_proto_rawDesc = nil + file_services_model_delegate_proto_goTypes = nil + file_services_model_delegate_proto_depIdxs = nil +} diff --git a/grpc/model_delegate_grpc.pb.go b/grpc/model_delegate_grpc.pb.go new file mode 100644 index 00000000..3ab1efa5 --- /dev/null +++ b/grpc/model_delegate_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/model_delegate.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ModelDelegateClient is the client API for ModelDelegate service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ModelDelegateClient interface { + Do(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type modelDelegateClient struct { + cc grpc.ClientConnInterface +} + +func NewModelDelegateClient(cc grpc.ClientConnInterface) ModelDelegateClient { + return &modelDelegateClient{cc} +} + +func (c *modelDelegateClient) Do(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.ModelDelegate/Do", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ModelDelegateServer is the server API for ModelDelegate service. +// All implementations must embed UnimplementedModelDelegateServer +// for forward compatibility +type ModelDelegateServer interface { + Do(context.Context, *Request) (*Response, error) + mustEmbedUnimplementedModelDelegateServer() +} + +// UnimplementedModelDelegateServer must be embedded to have forward compatible implementations. +type UnimplementedModelDelegateServer struct { +} + +func (UnimplementedModelDelegateServer) Do(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Do not implemented") +} +func (UnimplementedModelDelegateServer) mustEmbedUnimplementedModelDelegateServer() {} + +// UnsafeModelDelegateServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ModelDelegateServer will +// result in compilation errors. +type UnsafeModelDelegateServer interface { + mustEmbedUnimplementedModelDelegateServer() +} + +func RegisterModelDelegateServer(s grpc.ServiceRegistrar, srv ModelDelegateServer) { + s.RegisterService(&ModelDelegate_ServiceDesc, srv) +} + +func _ModelDelegate_Do_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelDelegateServer).Do(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.ModelDelegate/Do", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelDelegateServer).Do(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +// ModelDelegate_ServiceDesc is the grpc.ServiceDesc for ModelDelegate service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ModelDelegate_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.ModelDelegate", + HandlerType: (*ModelDelegateServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Do", + Handler: _ModelDelegate_Do_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "services/model_delegate.proto", +} diff --git a/grpc/model_service_v2_request.pb.go b/grpc/model_service_v2_request.pb.go new file mode 100644 index 00000000..b8958a9c --- /dev/null +++ b/grpc/model_service_v2_request.pb.go @@ -0,0 +1,1316 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/model_service_v2_request.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ModelServiceV2GetByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ModelServiceV2GetByIdRequest) Reset() { + *x = ModelServiceV2GetByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2GetByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2GetByIdRequest) ProtoMessage() {} + +func (x *ModelServiceV2GetByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2GetByIdRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2GetByIdRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{0} +} + +func (x *ModelServiceV2GetByIdRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2GetByIdRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2GetByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type ModelServiceV2GetOneRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + FindOptions []byte `protobuf:"bytes,4,opt,name=find_options,json=findOptions,proto3" json:"find_options,omitempty"` +} + +func (x *ModelServiceV2GetOneRequest) Reset() { + *x = ModelServiceV2GetOneRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2GetOneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2GetOneRequest) ProtoMessage() {} + +func (x *ModelServiceV2GetOneRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2GetOneRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2GetOneRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{1} +} + +func (x *ModelServiceV2GetOneRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2GetOneRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2GetOneRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +func (x *ModelServiceV2GetOneRequest) GetFindOptions() []byte { + if x != nil { + return x.FindOptions + } + return nil +} + +type ModelServiceV2GetManyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + FindOptions []byte `protobuf:"bytes,4,opt,name=find_options,json=findOptions,proto3" json:"find_options,omitempty"` +} + +func (x *ModelServiceV2GetManyRequest) Reset() { + *x = ModelServiceV2GetManyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2GetManyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2GetManyRequest) ProtoMessage() {} + +func (x *ModelServiceV2GetManyRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2GetManyRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2GetManyRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{2} +} + +func (x *ModelServiceV2GetManyRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2GetManyRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2GetManyRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +func (x *ModelServiceV2GetManyRequest) GetFindOptions() []byte { + if x != nil { + return x.FindOptions + } + return nil +} + +type ModelServiceV2DeleteByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *ModelServiceV2DeleteByIdRequest) Reset() { + *x = ModelServiceV2DeleteByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2DeleteByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2DeleteByIdRequest) ProtoMessage() {} + +func (x *ModelServiceV2DeleteByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2DeleteByIdRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2DeleteByIdRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{3} +} + +func (x *ModelServiceV2DeleteByIdRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2DeleteByIdRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2DeleteByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type ModelServiceV2DeleteOneRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *ModelServiceV2DeleteOneRequest) Reset() { + *x = ModelServiceV2DeleteOneRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2DeleteOneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2DeleteOneRequest) ProtoMessage() {} + +func (x *ModelServiceV2DeleteOneRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2DeleteOneRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2DeleteOneRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{4} +} + +func (x *ModelServiceV2DeleteOneRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2DeleteOneRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2DeleteOneRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +type ModelServiceV2DeleteManyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *ModelServiceV2DeleteManyRequest) Reset() { + *x = ModelServiceV2DeleteManyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2DeleteManyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2DeleteManyRequest) ProtoMessage() {} + +func (x *ModelServiceV2DeleteManyRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2DeleteManyRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2DeleteManyRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{5} +} + +func (x *ModelServiceV2DeleteManyRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2DeleteManyRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2DeleteManyRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +type ModelServiceV2UpdateByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` + Update []byte `protobuf:"bytes,4,opt,name=update,proto3" json:"update,omitempty"` +} + +func (x *ModelServiceV2UpdateByIdRequest) Reset() { + *x = ModelServiceV2UpdateByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2UpdateByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2UpdateByIdRequest) ProtoMessage() {} + +func (x *ModelServiceV2UpdateByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2UpdateByIdRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2UpdateByIdRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{6} +} + +func (x *ModelServiceV2UpdateByIdRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2UpdateByIdRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2UpdateByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelServiceV2UpdateByIdRequest) GetUpdate() []byte { + if x != nil { + return x.Update + } + return nil +} + +type ModelServiceV2UpdateOneRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + Update []byte `protobuf:"bytes,4,opt,name=update,proto3" json:"update,omitempty"` +} + +func (x *ModelServiceV2UpdateOneRequest) Reset() { + *x = ModelServiceV2UpdateOneRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2UpdateOneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2UpdateOneRequest) ProtoMessage() {} + +func (x *ModelServiceV2UpdateOneRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2UpdateOneRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2UpdateOneRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{7} +} + +func (x *ModelServiceV2UpdateOneRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2UpdateOneRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2UpdateOneRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +func (x *ModelServiceV2UpdateOneRequest) GetUpdate() []byte { + if x != nil { + return x.Update + } + return nil +} + +type ModelServiceV2UpdateManyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + Update []byte `protobuf:"bytes,4,opt,name=update,proto3" json:"update,omitempty"` +} + +func (x *ModelServiceV2UpdateManyRequest) Reset() { + *x = ModelServiceV2UpdateManyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2UpdateManyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2UpdateManyRequest) ProtoMessage() {} + +func (x *ModelServiceV2UpdateManyRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2UpdateManyRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2UpdateManyRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{8} +} + +func (x *ModelServiceV2UpdateManyRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2UpdateManyRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2UpdateManyRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +func (x *ModelServiceV2UpdateManyRequest) GetUpdate() []byte { + if x != nil { + return x.Update + } + return nil +} + +type ModelServiceV2ReplaceByIdRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Id string `protobuf:"bytes,3,opt,name=id,proto3" json:"id,omitempty"` + Model []byte `protobuf:"bytes,4,opt,name=model,proto3" json:"model,omitempty"` +} + +func (x *ModelServiceV2ReplaceByIdRequest) Reset() { + *x = ModelServiceV2ReplaceByIdRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2ReplaceByIdRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2ReplaceByIdRequest) ProtoMessage() {} + +func (x *ModelServiceV2ReplaceByIdRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2ReplaceByIdRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2ReplaceByIdRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{9} +} + +func (x *ModelServiceV2ReplaceByIdRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2ReplaceByIdRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2ReplaceByIdRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *ModelServiceV2ReplaceByIdRequest) GetModel() []byte { + if x != nil { + return x.Model + } + return nil +} + +type ModelServiceV2ReplaceOneRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` + Model []byte `protobuf:"bytes,4,opt,name=model,proto3" json:"model,omitempty"` +} + +func (x *ModelServiceV2ReplaceOneRequest) Reset() { + *x = ModelServiceV2ReplaceOneRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2ReplaceOneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2ReplaceOneRequest) ProtoMessage() {} + +func (x *ModelServiceV2ReplaceOneRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2ReplaceOneRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2ReplaceOneRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{10} +} + +func (x *ModelServiceV2ReplaceOneRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2ReplaceOneRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2ReplaceOneRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +func (x *ModelServiceV2ReplaceOneRequest) GetModel() []byte { + if x != nil { + return x.Model + } + return nil +} + +type ModelServiceV2InsertOneRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Model []byte `protobuf:"bytes,3,opt,name=model,proto3" json:"model,omitempty"` +} + +func (x *ModelServiceV2InsertOneRequest) Reset() { + *x = ModelServiceV2InsertOneRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2InsertOneRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2InsertOneRequest) ProtoMessage() {} + +func (x *ModelServiceV2InsertOneRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2InsertOneRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2InsertOneRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{11} +} + +func (x *ModelServiceV2InsertOneRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2InsertOneRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2InsertOneRequest) GetModel() []byte { + if x != nil { + return x.Model + } + return nil +} + +type ModelServiceV2InsertManyRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Models []byte `protobuf:"bytes,3,opt,name=models,proto3" json:"models,omitempty"` +} + +func (x *ModelServiceV2InsertManyRequest) Reset() { + *x = ModelServiceV2InsertManyRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2InsertManyRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2InsertManyRequest) ProtoMessage() {} + +func (x *ModelServiceV2InsertManyRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2InsertManyRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2InsertManyRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{12} +} + +func (x *ModelServiceV2InsertManyRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2InsertManyRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2InsertManyRequest) GetModels() []byte { + if x != nil { + return x.Models + } + return nil +} + +type ModelServiceV2CountRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + ModelType string `protobuf:"bytes,2,opt,name=model_type,json=modelType,proto3" json:"model_type,omitempty"` + Query []byte `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"` +} + +func (x *ModelServiceV2CountRequest) Reset() { + *x = ModelServiceV2CountRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_model_service_v2_request_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ModelServiceV2CountRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelServiceV2CountRequest) ProtoMessage() {} + +func (x *ModelServiceV2CountRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_model_service_v2_request_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelServiceV2CountRequest.ProtoReflect.Descriptor instead. +func (*ModelServiceV2CountRequest) Descriptor() ([]byte, []int) { + return file_entity_model_service_v2_request_proto_rawDescGZIP(), []int{13} +} + +func (x *ModelServiceV2CountRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *ModelServiceV2CountRequest) GetModelType() string { + if x != nil { + return x.ModelType + } + return "" +} + +func (x *ModelServiceV2CountRequest) GetQuery() []byte { + if x != nil { + return x.Query + } + return nil +} + +var File_entity_model_service_v2_request_proto protoreflect.FileDescriptor + +var file_entity_model_service_v2_request_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x76, 0x32, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0x68, 0x0a, + 0x1c, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x47, + 0x65, 0x74, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x90, 0x01, 0x0a, 0x1b, 0x4d, 0x6f, 0x64, 0x65, + 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x47, 0x65, 0x74, 0x4f, 0x6e, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, + 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x69, 0x6e, 0x64, 0x5f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x66, + 0x69, 0x6e, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x91, 0x01, 0x0a, 0x1c, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x47, 0x65, 0x74, + 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, + 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, + 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x66, + 0x69, 0x6e, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x6b, + 0x0a, 0x1f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x70, 0x0a, 0x1e, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x4f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x22, 0x71, 0x0a, + 0x1f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x22, 0x83, 0x01, 0x0a, 0x1f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x56, 0x32, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, + 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x88, 0x01, 0x0a, 0x1e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, + 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, + 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, + 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x22, 0x89, 0x01, 0x0a, 0x1f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x56, 0x32, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, + 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x22, 0x82, 0x01, + 0x0a, 0x20, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, + 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x42, 0x79, 0x49, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, + 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x14, 0x0a, 0x05, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6d, 0x6f, 0x64, + 0x65, 0x6c, 0x22, 0x87, 0x01, 0x0a, 0x1f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x56, 0x32, 0x52, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x4f, 0x6e, 0x65, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, + 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x22, 0x70, 0x0a, 0x1e, + 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, 0x49, 0x6e, + 0x73, 0x65, 0x72, 0x74, 0x4f, 0x6e, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, + 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f, 0x64, + 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x22, 0x73, + 0x0a, 0x1f, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x32, + 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x4d, 0x61, 0x6e, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x6d, 0x6f, 0x64, + 0x65, 0x6c, 0x73, 0x22, 0x6c, 0x0a, 0x1a, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x56, 0x32, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1d, 0x0a, 0x0a, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x71, + 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, + 0x79, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_entity_model_service_v2_request_proto_rawDescOnce sync.Once + file_entity_model_service_v2_request_proto_rawDescData = file_entity_model_service_v2_request_proto_rawDesc +) + +func file_entity_model_service_v2_request_proto_rawDescGZIP() []byte { + file_entity_model_service_v2_request_proto_rawDescOnce.Do(func() { + file_entity_model_service_v2_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_model_service_v2_request_proto_rawDescData) + }) + return file_entity_model_service_v2_request_proto_rawDescData +} + +var file_entity_model_service_v2_request_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_entity_model_service_v2_request_proto_goTypes = []interface{}{ + (*ModelServiceV2GetByIdRequest)(nil), // 0: grpc.ModelServiceV2GetByIdRequest + (*ModelServiceV2GetOneRequest)(nil), // 1: grpc.ModelServiceV2GetOneRequest + (*ModelServiceV2GetManyRequest)(nil), // 2: grpc.ModelServiceV2GetManyRequest + (*ModelServiceV2DeleteByIdRequest)(nil), // 3: grpc.ModelServiceV2DeleteByIdRequest + (*ModelServiceV2DeleteOneRequest)(nil), // 4: grpc.ModelServiceV2DeleteOneRequest + (*ModelServiceV2DeleteManyRequest)(nil), // 5: grpc.ModelServiceV2DeleteManyRequest + (*ModelServiceV2UpdateByIdRequest)(nil), // 6: grpc.ModelServiceV2UpdateByIdRequest + (*ModelServiceV2UpdateOneRequest)(nil), // 7: grpc.ModelServiceV2UpdateOneRequest + (*ModelServiceV2UpdateManyRequest)(nil), // 8: grpc.ModelServiceV2UpdateManyRequest + (*ModelServiceV2ReplaceByIdRequest)(nil), // 9: grpc.ModelServiceV2ReplaceByIdRequest + (*ModelServiceV2ReplaceOneRequest)(nil), // 10: grpc.ModelServiceV2ReplaceOneRequest + (*ModelServiceV2InsertOneRequest)(nil), // 11: grpc.ModelServiceV2InsertOneRequest + (*ModelServiceV2InsertManyRequest)(nil), // 12: grpc.ModelServiceV2InsertManyRequest + (*ModelServiceV2CountRequest)(nil), // 13: grpc.ModelServiceV2CountRequest +} +var file_entity_model_service_v2_request_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_model_service_v2_request_proto_init() } +func file_entity_model_service_v2_request_proto_init() { + if File_entity_model_service_v2_request_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_entity_model_service_v2_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2GetByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2GetOneRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2GetManyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2DeleteByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2DeleteOneRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2DeleteManyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2UpdateByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2UpdateOneRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2UpdateManyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2ReplaceByIdRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2ReplaceOneRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2InsertOneRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2InsertManyRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_entity_model_service_v2_request_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ModelServiceV2CountRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_model_service_v2_request_proto_rawDesc, + NumEnums: 0, + NumMessages: 14, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_model_service_v2_request_proto_goTypes, + DependencyIndexes: file_entity_model_service_v2_request_proto_depIdxs, + MessageInfos: file_entity_model_service_v2_request_proto_msgTypes, + }.Build() + File_entity_model_service_v2_request_proto = out.File + file_entity_model_service_v2_request_proto_rawDesc = nil + file_entity_model_service_v2_request_proto_goTypes = nil + file_entity_model_service_v2_request_proto_depIdxs = nil +} diff --git a/grpc/node.pb.go b/grpc/node.pb.go new file mode 100644 index 00000000..2e32c400 --- /dev/null +++ b/grpc/node.pb.go @@ -0,0 +1,251 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: models/node.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Node struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + XId string `protobuf:"bytes,1,opt,name=_id,json=Id,proto3" json:"_id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Ip string `protobuf:"bytes,3,opt,name=ip,proto3" json:"ip,omitempty"` + Port string `protobuf:"bytes,5,opt,name=port,proto3" json:"port,omitempty"` + Mac string `protobuf:"bytes,6,opt,name=mac,proto3" json:"mac,omitempty"` + Hostname string `protobuf:"bytes,7,opt,name=hostname,proto3" json:"hostname,omitempty"` + Description string `protobuf:"bytes,8,opt,name=description,proto3" json:"description,omitempty"` + Key string `protobuf:"bytes,9,opt,name=key,proto3" json:"key,omitempty"` + IsMaster bool `protobuf:"varint,11,opt,name=is_master,json=isMaster,proto3" json:"is_master,omitempty"` + UpdateTs string `protobuf:"bytes,12,opt,name=update_ts,json=updateTs,proto3" json:"update_ts,omitempty"` + CreateTs string `protobuf:"bytes,13,opt,name=create_ts,json=createTs,proto3" json:"create_ts,omitempty"` + UpdateTsUnix int64 `protobuf:"varint,14,opt,name=update_ts_unix,json=updateTsUnix,proto3" json:"update_ts_unix,omitempty"` +} + +func (x *Node) Reset() { + *x = Node{} + if protoimpl.UnsafeEnabled { + mi := &file_models_node_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Node) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Node) ProtoMessage() {} + +func (x *Node) ProtoReflect() protoreflect.Message { + mi := &file_models_node_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Node.ProtoReflect.Descriptor instead. +func (*Node) Descriptor() ([]byte, []int) { + return file_models_node_proto_rawDescGZIP(), []int{0} +} + +func (x *Node) GetXId() string { + if x != nil { + return x.XId + } + return "" +} + +func (x *Node) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Node) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +func (x *Node) GetPort() string { + if x != nil { + return x.Port + } + return "" +} + +func (x *Node) GetMac() string { + if x != nil { + return x.Mac + } + return "" +} + +func (x *Node) GetHostname() string { + if x != nil { + return x.Hostname + } + return "" +} + +func (x *Node) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Node) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Node) GetIsMaster() bool { + if x != nil { + return x.IsMaster + } + return false +} + +func (x *Node) GetUpdateTs() string { + if x != nil { + return x.UpdateTs + } + return "" +} + +func (x *Node) GetCreateTs() string { + if x != nil { + return x.CreateTs + } + return "" +} + +func (x *Node) GetUpdateTsUnix() int64 { + if x != nil { + return x.UpdateTsUnix + } + return 0 +} + +var File_models_node_proto protoreflect.FileDescriptor + +var file_models_node_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0xae, 0x02, 0x0a, 0x04, 0x4e, 0x6f, + 0x64, 0x65, 0x12, 0x0f, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6d, + 0x61, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 0x63, 0x12, 0x1a, 0x0a, + 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1b, 0x0a, + 0x09, 0x69, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x69, 0x73, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x5f, 0x74, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x73, 0x12, 0x24, 0x0a, 0x0e, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x73, 0x5f, 0x75, 0x6e, 0x69, 0x78, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x54, 0x73, 0x55, 0x6e, 0x69, 0x78, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, + 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_models_node_proto_rawDescOnce sync.Once + file_models_node_proto_rawDescData = file_models_node_proto_rawDesc +) + +func file_models_node_proto_rawDescGZIP() []byte { + file_models_node_proto_rawDescOnce.Do(func() { + file_models_node_proto_rawDescData = protoimpl.X.CompressGZIP(file_models_node_proto_rawDescData) + }) + return file_models_node_proto_rawDescData +} + +var file_models_node_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_models_node_proto_goTypes = []interface{}{ + (*Node)(nil), // 0: grpc.Node +} +var file_models_node_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_models_node_proto_init() } +func file_models_node_proto_init() { + if File_models_node_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_models_node_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Node); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_models_node_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_models_node_proto_goTypes, + DependencyIndexes: file_models_node_proto_depIdxs, + MessageInfos: file_models_node_proto_msgTypes, + }.Build() + File_models_node_proto = out.File + file_models_node_proto_rawDesc = nil + file_models_node_proto_goTypes = nil + file_models_node_proto_depIdxs = nil +} diff --git a/grpc/node_info.pb.go b/grpc/node_info.pb.go new file mode 100644 index 00000000..135ddcbf --- /dev/null +++ b/grpc/node_info.pb.go @@ -0,0 +1,156 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/node_info.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type NodeInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + IsMaster bool `protobuf:"varint,2,opt,name=is_master,json=isMaster,proto3" json:"is_master,omitempty"` +} + +func (x *NodeInfo) Reset() { + *x = NodeInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_node_info_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NodeInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NodeInfo) ProtoMessage() {} + +func (x *NodeInfo) ProtoReflect() protoreflect.Message { + mi := &file_entity_node_info_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NodeInfo.ProtoReflect.Descriptor instead. +func (*NodeInfo) Descriptor() ([]byte, []int) { + return file_entity_node_info_proto_rawDescGZIP(), []int{0} +} + +func (x *NodeInfo) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *NodeInfo) GetIsMaster() bool { + if x != nil { + return x.IsMaster + } + return false +} + +var File_entity_node_info_proto protoreflect.FileDescriptor + +var file_entity_node_info_proto_rawDesc = []byte{ + 0x0a, 0x16, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x6e, + 0x66, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0x39, + 0x0a, 0x08, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, + 0x69, 0x73, 0x5f, 0x6d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x69, 0x73, 0x4d, 0x61, 0x73, 0x74, 0x65, 0x72, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, + 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_node_info_proto_rawDescOnce sync.Once + file_entity_node_info_proto_rawDescData = file_entity_node_info_proto_rawDesc +) + +func file_entity_node_info_proto_rawDescGZIP() []byte { + file_entity_node_info_proto_rawDescOnce.Do(func() { + file_entity_node_info_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_node_info_proto_rawDescData) + }) + return file_entity_node_info_proto_rawDescData +} + +var file_entity_node_info_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_entity_node_info_proto_goTypes = []interface{}{ + (*NodeInfo)(nil), // 0: grpc.NodeInfo +} +var file_entity_node_info_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_node_info_proto_init() } +func file_entity_node_info_proto_init() { + if File_entity_node_info_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_entity_node_info_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NodeInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_node_info_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_node_info_proto_goTypes, + DependencyIndexes: file_entity_node_info_proto_depIdxs, + MessageInfos: file_entity_node_info_proto_msgTypes, + }.Build() + File_entity_node_info_proto = out.File + file_entity_node_info_proto_rawDesc = nil + file_entity_node_info_proto_goTypes = nil + file_entity_node_info_proto_depIdxs = nil +} diff --git a/grpc/node_service.pb.go b/grpc/node_service.pb.go new file mode 100644 index 00000000..ebed2559 --- /dev/null +++ b/grpc/node_service.pb.go @@ -0,0 +1,104 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/node_service.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_node_service_proto protoreflect.FileDescriptor + +var file_services_node_service_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, + 0x72, 0x70, 0x63, 0x1a, 0x14, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xfa, 0x01, + 0x0a, 0x0b, 0x4e, 0x6f, 0x64, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2b, 0x0a, + 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x30, 0x0a, 0x0d, 0x53, 0x65, + 0x6e, 0x64, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x0d, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x27, 0x0a, 0x04, + 0x50, 0x69, 0x6e, 0x67, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x2e, 0x0a, 0x0b, 0x55, 0x6e, + 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, + 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_services_node_service_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: grpc.Request + (*Response)(nil), // 1: grpc.Response + (*StreamMessage)(nil), // 2: grpc.StreamMessage +} +var file_services_node_service_proto_depIdxs = []int32{ + 0, // 0: grpc.NodeService.Register:input_type -> grpc.Request + 0, // 1: grpc.NodeService.SendHeartbeat:input_type -> grpc.Request + 0, // 2: grpc.NodeService.Ping:input_type -> grpc.Request + 0, // 3: grpc.NodeService.Subscribe:input_type -> grpc.Request + 0, // 4: grpc.NodeService.Unsubscribe:input_type -> grpc.Request + 1, // 5: grpc.NodeService.Register:output_type -> grpc.Response + 1, // 6: grpc.NodeService.SendHeartbeat:output_type -> grpc.Response + 1, // 7: grpc.NodeService.Ping:output_type -> grpc.Response + 2, // 8: grpc.NodeService.Subscribe:output_type -> grpc.StreamMessage + 1, // 9: grpc.NodeService.Unsubscribe:output_type -> grpc.Response + 5, // [5:10] is the sub-list for method output_type + 0, // [0:5] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_node_service_proto_init() } +func file_services_node_service_proto_init() { + if File_services_node_service_proto != nil { + return + } + file_entity_request_proto_init() + file_entity_response_proto_init() + file_entity_stream_message_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_node_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_node_service_proto_goTypes, + DependencyIndexes: file_services_node_service_proto_depIdxs, + }.Build() + File_services_node_service_proto = out.File + file_services_node_service_proto_rawDesc = nil + file_services_node_service_proto_goTypes = nil + file_services_node_service_proto_depIdxs = nil +} diff --git a/grpc/node_service_grpc.pb.go b/grpc/node_service_grpc.pb.go new file mode 100644 index 00000000..1e0372f1 --- /dev/null +++ b/grpc/node_service_grpc.pb.go @@ -0,0 +1,277 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/node_service.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// NodeServiceClient is the client API for NodeService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type NodeServiceClient interface { + Register(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + SendHeartbeat(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + Subscribe(ctx context.Context, in *Request, opts ...grpc.CallOption) (NodeService_SubscribeClient, error) + Unsubscribe(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type nodeServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewNodeServiceClient(cc grpc.ClientConnInterface) NodeServiceClient { + return &nodeServiceClient{cc} +} + +func (c *nodeServiceClient) Register(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.NodeService/Register", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeServiceClient) SendHeartbeat(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.NodeService/SendHeartbeat", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeServiceClient) Ping(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.NodeService/Ping", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *nodeServiceClient) Subscribe(ctx context.Context, in *Request, opts ...grpc.CallOption) (NodeService_SubscribeClient, error) { + stream, err := c.cc.NewStream(ctx, &NodeService_ServiceDesc.Streams[0], "/grpc.NodeService/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &nodeServiceSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type NodeService_SubscribeClient interface { + Recv() (*StreamMessage, error) + grpc.ClientStream +} + +type nodeServiceSubscribeClient struct { + grpc.ClientStream +} + +func (x *nodeServiceSubscribeClient) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *nodeServiceClient) Unsubscribe(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.NodeService/Unsubscribe", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NodeServiceServer is the server API for NodeService service. +// All implementations must embed UnimplementedNodeServiceServer +// for forward compatibility +type NodeServiceServer interface { + Register(context.Context, *Request) (*Response, error) + SendHeartbeat(context.Context, *Request) (*Response, error) + Ping(context.Context, *Request) (*Response, error) + Subscribe(*Request, NodeService_SubscribeServer) error + Unsubscribe(context.Context, *Request) (*Response, error) + mustEmbedUnimplementedNodeServiceServer() +} + +// UnimplementedNodeServiceServer must be embedded to have forward compatible implementations. +type UnimplementedNodeServiceServer struct { +} + +func (UnimplementedNodeServiceServer) Register(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedNodeServiceServer) SendHeartbeat(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendHeartbeat not implemented") +} +func (UnimplementedNodeServiceServer) Ping(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") +} +func (UnimplementedNodeServiceServer) Subscribe(*Request, NodeService_SubscribeServer) error { + return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") +} +func (UnimplementedNodeServiceServer) Unsubscribe(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Unsubscribe not implemented") +} +func (UnimplementedNodeServiceServer) mustEmbedUnimplementedNodeServiceServer() {} + +// UnsafeNodeServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NodeServiceServer will +// result in compilation errors. +type UnsafeNodeServiceServer interface { + mustEmbedUnimplementedNodeServiceServer() +} + +func RegisterNodeServiceServer(s grpc.ServiceRegistrar, srv NodeServiceServer) { + s.RegisterService(&NodeService_ServiceDesc, srv) +} + +func _NodeService_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeServiceServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.NodeService/Register", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeServiceServer).Register(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeService_SendHeartbeat_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeServiceServer).SendHeartbeat(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.NodeService/SendHeartbeat", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeServiceServer).SendHeartbeat(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeServiceServer).Ping(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.NodeService/Ping", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeServiceServer).Ping(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _NodeService_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(Request) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(NodeServiceServer).Subscribe(m, &nodeServiceSubscribeServer{stream}) +} + +type NodeService_SubscribeServer interface { + Send(*StreamMessage) error + grpc.ServerStream +} + +type nodeServiceSubscribeServer struct { + grpc.ServerStream +} + +func (x *nodeServiceSubscribeServer) Send(m *StreamMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _NodeService_Unsubscribe_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NodeServiceServer).Unsubscribe(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.NodeService/Unsubscribe", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NodeServiceServer).Unsubscribe(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +// NodeService_ServiceDesc is the grpc.ServiceDesc for NodeService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NodeService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.NodeService", + HandlerType: (*NodeServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Register", + Handler: _NodeService_Register_Handler, + }, + { + MethodName: "SendHeartbeat", + Handler: _NodeService_SendHeartbeat_Handler, + }, + { + MethodName: "Ping", + Handler: _NodeService_Ping_Handler, + }, + { + MethodName: "Unsubscribe", + Handler: _NodeService_Unsubscribe_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Subscribe", + Handler: _NodeService_Subscribe_Handler, + ServerStreams: true, + }, + }, + Metadata: "services/node_service.proto", +} diff --git a/grpc/plugin_request.pb.go b/grpc/plugin_request.pb.go new file mode 100644 index 00000000..3db72ec1 --- /dev/null +++ b/grpc/plugin_request.pb.go @@ -0,0 +1,166 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/plugin_request.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type PluginRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + NodeKey string `protobuf:"bytes,2,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *PluginRequest) Reset() { + *x = PluginRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_plugin_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PluginRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PluginRequest) ProtoMessage() {} + +func (x *PluginRequest) ProtoReflect() protoreflect.Message { + mi := &file_entity_plugin_request_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PluginRequest.ProtoReflect.Descriptor instead. +func (*PluginRequest) Descriptor() ([]byte, []int) { + return file_entity_plugin_request_proto_rawDescGZIP(), []int{0} +} + +func (x *PluginRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *PluginRequest) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *PluginRequest) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_entity_plugin_request_proto protoreflect.FileDescriptor + +var file_entity_plugin_request_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, + 0x72, 0x70, 0x63, 0x22, 0x52, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, + 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, + 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_plugin_request_proto_rawDescOnce sync.Once + file_entity_plugin_request_proto_rawDescData = file_entity_plugin_request_proto_rawDesc +) + +func file_entity_plugin_request_proto_rawDescGZIP() []byte { + file_entity_plugin_request_proto_rawDescOnce.Do(func() { + file_entity_plugin_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_plugin_request_proto_rawDescData) + }) + return file_entity_plugin_request_proto_rawDescData +} + +var file_entity_plugin_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_entity_plugin_request_proto_goTypes = []interface{}{ + (*PluginRequest)(nil), // 0: grpc.PluginRequest +} +var file_entity_plugin_request_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_plugin_request_proto_init() } +func file_entity_plugin_request_proto_init() { + if File_entity_plugin_request_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_entity_plugin_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PluginRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_plugin_request_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_plugin_request_proto_goTypes, + DependencyIndexes: file_entity_plugin_request_proto_depIdxs, + MessageInfos: file_entity_plugin_request_proto_msgTypes, + }.Build() + File_entity_plugin_request_proto = out.File + file_entity_plugin_request_proto_rawDesc = nil + file_entity_plugin_request_proto_goTypes = nil + file_entity_plugin_request_proto_depIdxs = nil +} diff --git a/grpc/plugin_service.pb.go b/grpc/plugin_service.pb.go new file mode 100644 index 00000000..d8974eef --- /dev/null +++ b/grpc/plugin_service.pb.go @@ -0,0 +1,96 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/plugin_service.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_plugin_service_proto protoreflect.FileDescriptor + +var file_services_plugin_service_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xb5, 0x01, 0x0a, 0x0d, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x39, 0x0a, 0x09, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, + 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x36, 0x0a, 0x04, 0x50, 0x6f, 0x6c, 0x6c, 0x12, 0x13, + 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x1a, 0x13, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, + 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var file_services_plugin_service_proto_goTypes = []interface{}{ + (*PluginRequest)(nil), // 0: grpc.PluginRequest + (*StreamMessage)(nil), // 1: grpc.StreamMessage + (*Response)(nil), // 2: grpc.Response +} +var file_services_plugin_service_proto_depIdxs = []int32{ + 0, // 0: grpc.PluginService.Register:input_type -> grpc.PluginRequest + 0, // 1: grpc.PluginService.Subscribe:input_type -> grpc.PluginRequest + 1, // 2: grpc.PluginService.Poll:input_type -> grpc.StreamMessage + 2, // 3: grpc.PluginService.Register:output_type -> grpc.Response + 1, // 4: grpc.PluginService.Subscribe:output_type -> grpc.StreamMessage + 1, // 5: grpc.PluginService.Poll:output_type -> grpc.StreamMessage + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_plugin_service_proto_init() } +func file_services_plugin_service_proto_init() { + if File_services_plugin_service_proto != nil { + return + } + file_entity_plugin_request_proto_init() + file_entity_response_proto_init() + file_entity_stream_message_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_plugin_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_plugin_service_proto_goTypes, + DependencyIndexes: file_services_plugin_service_proto_depIdxs, + }.Build() + File_services_plugin_service_proto = out.File + file_services_plugin_service_proto_rawDesc = nil + file_services_plugin_service_proto_goTypes = nil + file_services_plugin_service_proto_depIdxs = nil +} diff --git a/grpc/plugin_service_grpc.pb.go b/grpc/plugin_service_grpc.pb.go new file mode 100644 index 00000000..06b93262 --- /dev/null +++ b/grpc/plugin_service_grpc.pb.go @@ -0,0 +1,237 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/plugin_service.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// PluginServiceClient is the client API for PluginService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PluginServiceClient interface { + Register(ctx context.Context, in *PluginRequest, opts ...grpc.CallOption) (*Response, error) + Subscribe(ctx context.Context, in *PluginRequest, opts ...grpc.CallOption) (PluginService_SubscribeClient, error) + Poll(ctx context.Context, opts ...grpc.CallOption) (PluginService_PollClient, error) +} + +type pluginServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPluginServiceClient(cc grpc.ClientConnInterface) PluginServiceClient { + return &pluginServiceClient{cc} +} + +func (c *pluginServiceClient) Register(ctx context.Context, in *PluginRequest, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.PluginService/Register", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *pluginServiceClient) Subscribe(ctx context.Context, in *PluginRequest, opts ...grpc.CallOption) (PluginService_SubscribeClient, error) { + stream, err := c.cc.NewStream(ctx, &PluginService_ServiceDesc.Streams[0], "/grpc.PluginService/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &pluginServiceSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type PluginService_SubscribeClient interface { + Recv() (*StreamMessage, error) + grpc.ClientStream +} + +type pluginServiceSubscribeClient struct { + grpc.ClientStream +} + +func (x *pluginServiceSubscribeClient) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *pluginServiceClient) Poll(ctx context.Context, opts ...grpc.CallOption) (PluginService_PollClient, error) { + stream, err := c.cc.NewStream(ctx, &PluginService_ServiceDesc.Streams[1], "/grpc.PluginService/Poll", opts...) + if err != nil { + return nil, err + } + x := &pluginServicePollClient{stream} + return x, nil +} + +type PluginService_PollClient interface { + Send(*StreamMessage) error + Recv() (*StreamMessage, error) + grpc.ClientStream +} + +type pluginServicePollClient struct { + grpc.ClientStream +} + +func (x *pluginServicePollClient) Send(m *StreamMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *pluginServicePollClient) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// PluginServiceServer is the server API for PluginService service. +// All implementations must embed UnimplementedPluginServiceServer +// for forward compatibility +type PluginServiceServer interface { + Register(context.Context, *PluginRequest) (*Response, error) + Subscribe(*PluginRequest, PluginService_SubscribeServer) error + Poll(PluginService_PollServer) error + mustEmbedUnimplementedPluginServiceServer() +} + +// UnimplementedPluginServiceServer must be embedded to have forward compatible implementations. +type UnimplementedPluginServiceServer struct { +} + +func (UnimplementedPluginServiceServer) Register(context.Context, *PluginRequest) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Register not implemented") +} +func (UnimplementedPluginServiceServer) Subscribe(*PluginRequest, PluginService_SubscribeServer) error { + return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") +} +func (UnimplementedPluginServiceServer) Poll(PluginService_PollServer) error { + return status.Errorf(codes.Unimplemented, "method Poll not implemented") +} +func (UnimplementedPluginServiceServer) mustEmbedUnimplementedPluginServiceServer() {} + +// UnsafePluginServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PluginServiceServer will +// result in compilation errors. +type UnsafePluginServiceServer interface { + mustEmbedUnimplementedPluginServiceServer() +} + +func RegisterPluginServiceServer(s grpc.ServiceRegistrar, srv PluginServiceServer) { + s.RegisterService(&PluginService_ServiceDesc, srv) +} + +func _PluginService_Register_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PluginRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PluginServiceServer).Register(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.PluginService/Register", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PluginServiceServer).Register(ctx, req.(*PluginRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PluginService_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(PluginRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(PluginServiceServer).Subscribe(m, &pluginServiceSubscribeServer{stream}) +} + +type PluginService_SubscribeServer interface { + Send(*StreamMessage) error + grpc.ServerStream +} + +type pluginServiceSubscribeServer struct { + grpc.ServerStream +} + +func (x *pluginServiceSubscribeServer) Send(m *StreamMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _PluginService_Poll_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(PluginServiceServer).Poll(&pluginServicePollServer{stream}) +} + +type PluginService_PollServer interface { + Send(*StreamMessage) error + Recv() (*StreamMessage, error) + grpc.ServerStream +} + +type pluginServicePollServer struct { + grpc.ServerStream +} + +func (x *pluginServicePollServer) Send(m *StreamMessage) error { + return x.ServerStream.SendMsg(m) +} + +func (x *pluginServicePollServer) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// PluginService_ServiceDesc is the grpc.ServiceDesc for PluginService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PluginService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.PluginService", + HandlerType: (*PluginServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Register", + Handler: _PluginService_Register_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Subscribe", + Handler: _PluginService_Subscribe_Handler, + ServerStreams: true, + }, + { + StreamName: "Poll", + Handler: _PluginService_Poll_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "services/plugin_service.proto", +} diff --git a/grpc/proto/entity/dependencies_service_v2_request.proto b/grpc/proto/entity/dependencies_service_v2_request.proto new file mode 100644 index 00000000..c7cfa79d --- /dev/null +++ b/grpc/proto/entity/dependencies_service_v2_request.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message Dependency { + string name = 1; + string version = 2; +} + +message DependenciesServiceV2ConnectRequest { + string node_key = 1; +} + +message DependenciesServiceV2SyncRequest { + string node_key = 1; + string lang = 2; + repeated Dependency dependencies = 3; +} + +message DependenciesServiceV2InstallRequest { + string node_key = 1; + string lang = 2; + repeated Dependency dependencies = 3; + string proxy = 4; +} + +message DependenciesServiceV2UninstallRequest { + string node_key = 1; + string lang = 2; + repeated Dependency dependencies = 3; +} \ No newline at end of file diff --git a/grpc/proto/entity/model_service_v2_request.proto b/grpc/proto/entity/model_service_v2_request.proto new file mode 100644 index 00000000..f2933ca7 --- /dev/null +++ b/grpc/proto/entity/model_service_v2_request.proto @@ -0,0 +1,95 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message ModelServiceV2GetByIdRequest { + string node_key = 1; + string model_type = 2; + string id = 3; +} + +message ModelServiceV2GetOneRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; + bytes find_options = 4; +} + +message ModelServiceV2GetManyRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; + bytes find_options = 4; +} + +message ModelServiceV2DeleteByIdRequest { + string node_key = 1; + string model_type = 2; + string id = 3; +} + +message ModelServiceV2DeleteOneRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; +} + +message ModelServiceV2DeleteManyRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; +} + +message ModelServiceV2UpdateByIdRequest { + string node_key = 1; + string model_type = 2; + string id = 3; + bytes update = 4; +} + +message ModelServiceV2UpdateOneRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; + bytes update = 4; +} + +message ModelServiceV2UpdateManyRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; + bytes update = 4; +} + +message ModelServiceV2ReplaceByIdRequest { + string node_key = 1; + string model_type = 2; + string id = 3; + bytes model = 4; +} + +message ModelServiceV2ReplaceOneRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; + bytes model = 4; +} + +message ModelServiceV2InsertOneRequest { + string node_key = 1; + string model_type = 2; + bytes model = 3; +} + +message ModelServiceV2InsertManyRequest { + string node_key = 1; + string model_type = 2; + bytes models = 3; +} + +message ModelServiceV2CountRequest { + string node_key = 1; + string model_type = 2; + bytes query = 3; +} diff --git a/grpc/proto/entity/node_info.proto b/grpc/proto/entity/node_info.proto new file mode 100644 index 00000000..e289d29d --- /dev/null +++ b/grpc/proto/entity/node_info.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message NodeInfo { + string key = 1; + bool is_master = 2; +} diff --git a/grpc/proto/entity/plugin_request.proto b/grpc/proto/entity/plugin_request.proto new file mode 100644 index 00000000..9359028e --- /dev/null +++ b/grpc/proto/entity/plugin_request.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message PluginRequest { + string name = 1; + string node_key = 2; + bytes data = 3; +} diff --git a/grpc/proto/entity/request.proto b/grpc/proto/entity/request.proto new file mode 100644 index 00000000..78b88e7d --- /dev/null +++ b/grpc/proto/entity/request.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message Request { + string node_key = 1; + bytes data = 2; +} diff --git a/grpc/proto/entity/response.proto b/grpc/proto/entity/response.proto new file mode 100644 index 00000000..9145a1b0 --- /dev/null +++ b/grpc/proto/entity/response.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "entity/response_code.proto"; + +package grpc; +option go_package = ".;grpc"; + +message Response { + ResponseCode code = 1; + string message = 2; + bytes data = 3; + string error = 4; + int64 total = 5; +} diff --git a/grpc/proto/entity/response_code.proto b/grpc/proto/entity/response_code.proto new file mode 100644 index 00000000..cb7398fc --- /dev/null +++ b/grpc/proto/entity/response_code.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +option go_package = ".;grpc"; + +enum ResponseCode { + OK = 0; + ERROR = 1; +} \ No newline at end of file diff --git a/grpc/proto/entity/stream_message.proto b/grpc/proto/entity/stream_message.proto new file mode 100644 index 00000000..44cd7af4 --- /dev/null +++ b/grpc/proto/entity/stream_message.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +import "entity/stream_message_code.proto"; + +package grpc; +option go_package = ".;grpc"; + +message StreamMessage { + StreamMessageCode code = 1; + string node_key = 2; + string key = 3; + string from = 4; + string to = 5; + bytes data = 6; + string error = 7; +} diff --git a/grpc/proto/entity/stream_message_code.proto b/grpc/proto/entity/stream_message_code.proto new file mode 100644 index 00000000..f2c41882 --- /dev/null +++ b/grpc/proto/entity/stream_message_code.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +enum StreamMessageCode { + // ping worker nodes to check their health + PING = 0; + // ask worker node(s) to run task with given id + RUN_TASK = 1; + // ask worker node(s) to cancel task with given id + CANCEL_TASK = 2; + // insert data + INSERT_DATA = 3; + // insert logs + INSERT_LOGS = 4; + // send event + SEND_EVENT = 5; + // install plugin + INSTALL_PLUGIN = 6; + // uninstall plugin + UNINSTALL_PLUGIN = 7; + // start plugin + START_PLUGIN = 8; + // stop plugin + STOP_PLUGIN = 9; + // connect + CONNECT = 10; + // disconnect + DISCONNECT = 11; + // send + SEND = 12; +} diff --git a/grpc/proto/entity/stream_message_data_task.proto b/grpc/proto/entity/stream_message_data_task.proto new file mode 100644 index 00000000..b6c082d9 --- /dev/null +++ b/grpc/proto/entity/stream_message_data_task.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message StreamMessageDataTask { + string task_id = 1; + string data = 2; +} diff --git a/grpc/proto/models/node.proto b/grpc/proto/models/node.proto new file mode 100644 index 00000000..aa5312cc --- /dev/null +++ b/grpc/proto/models/node.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message Node { + string _id = 1; + string name = 2; + string ip = 3; + string port = 5; + string mac = 6; + string hostname = 7; + string description = 8; + string key = 9; + bool is_master = 11; + string update_ts = 12; + string create_ts = 13; + int64 update_ts_unix = 14; +} diff --git a/grpc/proto/models/task.proto b/grpc/proto/models/task.proto new file mode 100644 index 00000000..52ea7056 --- /dev/null +++ b/grpc/proto/models/task.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package grpc; +option go_package = ".;grpc"; + +message Task { + string _id = 1; + string spider_id = 2; + string status = 5; + string node_id = 6; + string cmd = 8; + string param = 9; + string error = 10; + int32 pid = 16; + string run_type = 17; + string schedule_id = 18; + string type = 19; +} \ No newline at end of file diff --git a/grpc/proto/services/dependency_service_v2.proto b/grpc/proto/services/dependency_service_v2.proto new file mode 100644 index 00000000..dea310d6 --- /dev/null +++ b/grpc/proto/services/dependency_service_v2.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "entity/dependencies_service_v2_request.proto"; +import "entity/response.proto"; + +package grpc; +option go_package = ".;grpc"; + +service DependencyServiceV2 { + rpc Connect(stream DependenciesServiceV2ConnectRequest) returns (Response){}; + rpc Sync(DependenciesServiceV2SyncRequest) returns (Response){}; + rpc Install(stream DependenciesServiceV2InstallRequest) returns (stream Response){}; + rpc UninstallDependencies(stream DependenciesServiceV2UninstallRequest) returns (stream Response){}; +} diff --git a/grpc/proto/services/message_service.proto b/grpc/proto/services/message_service.proto new file mode 100644 index 00000000..41207403 --- /dev/null +++ b/grpc/proto/services/message_service.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +import "entity/stream_message.proto"; + +package grpc; +option go_package = ".;grpc"; + +service MessageService { + rpc Connect(stream StreamMessage) returns (stream StreamMessage){}; +} diff --git a/grpc/proto/services/model_base_service.proto b/grpc/proto/services/model_base_service.proto new file mode 100644 index 00000000..0836659a --- /dev/null +++ b/grpc/proto/services/model_base_service.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +import "entity/request.proto"; +import "entity/response.proto"; + +package grpc; +option go_package = ".;grpc"; + +service ModelBaseService { + rpc GetById(Request) returns (Response){}; + rpc Get(Request) returns (Response){}; + rpc GetList(Request) returns (Response){}; + rpc DeleteById(Request) returns (Response){}; + rpc Delete(Request) returns (Response){}; + rpc DeleteList(Request) returns (Response){}; + rpc ForceDeleteList(Request) returns (Response){}; + rpc UpdateById(Request) returns (Response){}; + rpc Update(Request) returns (Response){}; + rpc UpdateDoc(Request) returns (Response){}; + rpc Insert(Request) returns (Response){}; + rpc Count(Request) returns (Response){}; +} diff --git a/grpc/proto/services/model_base_service_v2.proto b/grpc/proto/services/model_base_service_v2.proto new file mode 100644 index 00000000..e0b309f7 --- /dev/null +++ b/grpc/proto/services/model_base_service_v2.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +import "entity/model_service_v2_request.proto"; +import "entity/response.proto"; + +package grpc; +option go_package = ".;grpc"; + +service ModelBaseServiceV2 { + rpc GetById(ModelServiceV2GetByIdRequest) returns (Response){}; + rpc GetOne(ModelServiceV2GetOneRequest) returns (Response){}; + rpc GetMany(ModelServiceV2GetManyRequest) returns (Response){}; + rpc DeleteById(ModelServiceV2DeleteByIdRequest) returns (Response){}; + rpc DeleteOne(ModelServiceV2DeleteOneRequest) returns (Response){}; + rpc DeleteMany(ModelServiceV2DeleteManyRequest) returns (Response){}; + rpc UpdateById(ModelServiceV2UpdateByIdRequest) returns (Response){}; + rpc UpdateOne(ModelServiceV2UpdateOneRequest) returns (Response){}; + rpc UpdateMany(ModelServiceV2UpdateManyRequest) returns (Response){}; + rpc ReplaceById(ModelServiceV2ReplaceByIdRequest) returns (Response){}; + rpc ReplaceOne(ModelServiceV2ReplaceOneRequest) returns (Response){}; + rpc InsertOne(ModelServiceV2InsertOneRequest) returns (Response){}; + rpc InsertMany(ModelServiceV2InsertManyRequest) returns (Response){}; + rpc Count(ModelServiceV2CountRequest) returns (Response){}; +} diff --git a/grpc/proto/services/model_delegate.proto b/grpc/proto/services/model_delegate.proto new file mode 100644 index 00000000..1b60477c --- /dev/null +++ b/grpc/proto/services/model_delegate.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +import "entity/request.proto"; +import "entity/response.proto"; + +package grpc; +option go_package = ".;grpc"; + +service ModelDelegate { + rpc Do(Request) returns (Response){}; +} diff --git a/grpc/proto/services/node_service.proto b/grpc/proto/services/node_service.proto new file mode 100644 index 00000000..4b43d083 --- /dev/null +++ b/grpc/proto/services/node_service.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +import "entity/request.proto"; +import "entity/response.proto"; +import "entity/stream_message.proto"; + +package grpc; +option go_package = ".;grpc"; + +service NodeService { + rpc Register(Request) returns (Response){}; + rpc SendHeartbeat(Request) returns (Response){}; + rpc Ping(Request) returns (Response){}; + rpc Subscribe(Request) returns (stream StreamMessage){}; + rpc Unsubscribe(Request) returns (Response){}; +} diff --git a/grpc/proto/services/plugin_service.proto b/grpc/proto/services/plugin_service.proto new file mode 100644 index 00000000..5e41977e --- /dev/null +++ b/grpc/proto/services/plugin_service.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "entity/plugin_request.proto"; +import "entity/response.proto"; +import "entity/stream_message.proto"; + +package grpc; +option go_package = ".;grpc"; + +service PluginService { + rpc Register(PluginRequest) returns (Response){}; + rpc Subscribe(PluginRequest) returns (stream StreamMessage){}; + rpc Poll(stream StreamMessage) returns (stream StreamMessage){}; +} diff --git a/grpc/proto/services/task_service.proto b/grpc/proto/services/task_service.proto new file mode 100644 index 00000000..f004e565 --- /dev/null +++ b/grpc/proto/services/task_service.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +import "entity/request.proto"; +import "entity/response.proto"; +import "entity/stream_message.proto"; + +package grpc; +option go_package = ".;grpc"; + +service TaskService { + rpc Subscribe(stream StreamMessage) returns (Response){}; + rpc Fetch(Request) returns (Response){}; + rpc SendNotification(Request) returns (Response){}; +} diff --git a/grpc/request.pb.go b/grpc/request.pb.go new file mode 100644 index 00000000..3b541554 --- /dev/null +++ b/grpc/request.pb.go @@ -0,0 +1,156 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/request.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Request struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NodeKey string `protobuf:"bytes,1,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *Request) Reset() { + *x = Request{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_request_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Request) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Request) ProtoMessage() {} + +func (x *Request) ProtoReflect() protoreflect.Message { + mi := &file_entity_request_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Request.ProtoReflect.Descriptor instead. +func (*Request) Descriptor() ([]byte, []int) { + return file_entity_request_proto_rawDescGZIP(), []int{0} +} + +func (x *Request) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *Request) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +var File_entity_request_proto protoreflect.FileDescriptor + +var file_entity_request_proto_rawDesc = []byte{ + 0x0a, 0x14, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0x38, 0x0a, 0x07, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, + 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_request_proto_rawDescOnce sync.Once + file_entity_request_proto_rawDescData = file_entity_request_proto_rawDesc +) + +func file_entity_request_proto_rawDescGZIP() []byte { + file_entity_request_proto_rawDescOnce.Do(func() { + file_entity_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_request_proto_rawDescData) + }) + return file_entity_request_proto_rawDescData +} + +var file_entity_request_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_entity_request_proto_goTypes = []interface{}{ + (*Request)(nil), // 0: grpc.Request +} +var file_entity_request_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_request_proto_init() } +func file_entity_request_proto_init() { + if File_entity_request_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_entity_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Request); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_request_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_request_proto_goTypes, + DependencyIndexes: file_entity_request_proto_depIdxs, + MessageInfos: file_entity_request_proto_msgTypes, + }.Build() + File_entity_request_proto = out.File + file_entity_request_proto_rawDesc = nil + file_entity_request_proto_goTypes = nil + file_entity_request_proto_depIdxs = nil +} diff --git a/grpc/response.pb.go b/grpc/response.pb.go new file mode 100644 index 00000000..ad32f869 --- /dev/null +++ b/grpc/response.pb.go @@ -0,0 +1,190 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/response.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Response struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code ResponseCode `protobuf:"varint,1,opt,name=code,proto3,enum=ResponseCode" json:"code,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + Total int64 `protobuf:"varint,5,opt,name=total,proto3" json:"total,omitempty"` +} + +func (x *Response) Reset() { + *x = Response{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_response_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Response) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Response) ProtoMessage() {} + +func (x *Response) ProtoReflect() protoreflect.Message { + mi := &file_entity_response_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Response.ProtoReflect.Descriptor instead. +func (*Response) Descriptor() ([]byte, []int) { + return file_entity_response_proto_rawDescGZIP(), []int{0} +} + +func (x *Response) GetCode() ResponseCode { + if x != nil { + return x.Code + } + return ResponseCode_OK +} + +func (x *Response) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +func (x *Response) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *Response) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *Response) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +var File_entity_response_proto protoreflect.FileDescriptor + +var file_entity_response_proto_rawDesc = []byte{ + 0x0a, 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x1a, 0x1a, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x01, 0x0a, 0x08, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0d, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, + 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, + 0x74, 0x61, 0x6c, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_response_proto_rawDescOnce sync.Once + file_entity_response_proto_rawDescData = file_entity_response_proto_rawDesc +) + +func file_entity_response_proto_rawDescGZIP() []byte { + file_entity_response_proto_rawDescOnce.Do(func() { + file_entity_response_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_response_proto_rawDescData) + }) + return file_entity_response_proto_rawDescData +} + +var file_entity_response_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_entity_response_proto_goTypes = []interface{}{ + (*Response)(nil), // 0: grpc.Response + (ResponseCode)(0), // 1: ResponseCode +} +var file_entity_response_proto_depIdxs = []int32{ + 1, // 0: grpc.Response.code:type_name -> ResponseCode + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_entity_response_proto_init() } +func file_entity_response_proto_init() { + if File_entity_response_proto != nil { + return + } + file_entity_response_code_proto_init() + if !protoimpl.UnsafeEnabled { + file_entity_response_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_response_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_response_proto_goTypes, + DependencyIndexes: file_entity_response_proto_depIdxs, + MessageInfos: file_entity_response_proto_msgTypes, + }.Build() + File_entity_response_proto = out.File + file_entity_response_proto_rawDesc = nil + file_entity_response_proto_goTypes = nil + file_entity_response_proto_depIdxs = nil +} diff --git a/grpc/response_code.pb.go b/grpc/response_code.pb.go new file mode 100644 index 00000000..9c9700a8 --- /dev/null +++ b/grpc/response_code.pb.go @@ -0,0 +1,132 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/response_code.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type ResponseCode int32 + +const ( + ResponseCode_OK ResponseCode = 0 + ResponseCode_ERROR ResponseCode = 1 +) + +// Enum value maps for ResponseCode. +var ( + ResponseCode_name = map[int32]string{ + 0: "OK", + 1: "ERROR", + } + ResponseCode_value = map[string]int32{ + "OK": 0, + "ERROR": 1, + } +) + +func (x ResponseCode) Enum() *ResponseCode { + p := new(ResponseCode) + *p = x + return p +} + +func (x ResponseCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ResponseCode) Descriptor() protoreflect.EnumDescriptor { + return file_entity_response_code_proto_enumTypes[0].Descriptor() +} + +func (ResponseCode) Type() protoreflect.EnumType { + return &file_entity_response_code_proto_enumTypes[0] +} + +func (x ResponseCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ResponseCode.Descriptor instead. +func (ResponseCode) EnumDescriptor() ([]byte, []int) { + return file_entity_response_code_proto_rawDescGZIP(), []int{0} +} + +var File_entity_response_code_proto protoreflect.FileDescriptor + +var file_entity_response_code_proto_rawDesc = []byte{ + 0x0a, 0x1a, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2a, 0x21, 0x0a, 0x0c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x06, 0x0a, 0x02, + 0x4f, 0x4b, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x42, + 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_entity_response_code_proto_rawDescOnce sync.Once + file_entity_response_code_proto_rawDescData = file_entity_response_code_proto_rawDesc +) + +func file_entity_response_code_proto_rawDescGZIP() []byte { + file_entity_response_code_proto_rawDescOnce.Do(func() { + file_entity_response_code_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_response_code_proto_rawDescData) + }) + return file_entity_response_code_proto_rawDescData +} + +var file_entity_response_code_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_entity_response_code_proto_goTypes = []interface{}{ + (ResponseCode)(0), // 0: ResponseCode +} +var file_entity_response_code_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_response_code_proto_init() } +func file_entity_response_code_proto_init() { + if File_entity_response_code_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_response_code_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_response_code_proto_goTypes, + DependencyIndexes: file_entity_response_code_proto_depIdxs, + EnumInfos: file_entity_response_code_proto_enumTypes, + }.Build() + File_entity_response_code_proto = out.File + file_entity_response_code_proto_rawDesc = nil + file_entity_response_code_proto_goTypes = nil + file_entity_response_code_proto_depIdxs = nil +} diff --git a/grpc/stream_message.pb.go b/grpc/stream_message.pb.go new file mode 100644 index 00000000..2e563dfc --- /dev/null +++ b/grpc/stream_message.pb.go @@ -0,0 +1,210 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/stream_message.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type StreamMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Code StreamMessageCode `protobuf:"varint,1,opt,name=code,proto3,enum=grpc.StreamMessageCode" json:"code,omitempty"` + NodeKey string `protobuf:"bytes,2,opt,name=node_key,json=nodeKey,proto3" json:"node_key,omitempty"` + Key string `protobuf:"bytes,3,opt,name=key,proto3" json:"key,omitempty"` + From string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"` + To string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"` + Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"` + Error string `protobuf:"bytes,7,opt,name=error,proto3" json:"error,omitempty"` +} + +func (x *StreamMessage) Reset() { + *x = StreamMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_stream_message_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamMessage) ProtoMessage() {} + +func (x *StreamMessage) ProtoReflect() protoreflect.Message { + mi := &file_entity_stream_message_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamMessage.ProtoReflect.Descriptor instead. +func (*StreamMessage) Descriptor() ([]byte, []int) { + return file_entity_stream_message_proto_rawDescGZIP(), []int{0} +} + +func (x *StreamMessage) GetCode() StreamMessageCode { + if x != nil { + return x.Code + } + return StreamMessageCode_PING +} + +func (x *StreamMessage) GetNodeKey() string { + if x != nil { + return x.NodeKey + } + return "" +} + +func (x *StreamMessage) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *StreamMessage) GetFrom() string { + if x != nil { + return x.From + } + return "" +} + +func (x *StreamMessage) GetTo() string { + if x != nil { + return x.To + } + return "" +} + +func (x *StreamMessage) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *StreamMessage) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +var File_entity_stream_message_proto protoreflect.FileDescriptor + +var file_entity_stream_message_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, + 0x72, 0x70, 0x63, 0x1a, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb7, 0x01, 0x0a, 0x0d, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x2b, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, + 0x63, 0x6f, 0x64, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x6f, 0x64, 0x65, 0x4b, 0x65, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x42, + 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_entity_stream_message_proto_rawDescOnce sync.Once + file_entity_stream_message_proto_rawDescData = file_entity_stream_message_proto_rawDesc +) + +func file_entity_stream_message_proto_rawDescGZIP() []byte { + file_entity_stream_message_proto_rawDescOnce.Do(func() { + file_entity_stream_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_stream_message_proto_rawDescData) + }) + return file_entity_stream_message_proto_rawDescData +} + +var file_entity_stream_message_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_entity_stream_message_proto_goTypes = []interface{}{ + (*StreamMessage)(nil), // 0: grpc.StreamMessage + (StreamMessageCode)(0), // 1: grpc.StreamMessageCode +} +var file_entity_stream_message_proto_depIdxs = []int32{ + 1, // 0: grpc.StreamMessage.code:type_name -> grpc.StreamMessageCode + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_entity_stream_message_proto_init() } +func file_entity_stream_message_proto_init() { + if File_entity_stream_message_proto != nil { + return + } + file_entity_stream_message_code_proto_init() + if !protoimpl.UnsafeEnabled { + file_entity_stream_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_stream_message_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_stream_message_proto_goTypes, + DependencyIndexes: file_entity_stream_message_proto_depIdxs, + MessageInfos: file_entity_stream_message_proto_msgTypes, + }.Build() + File_entity_stream_message_proto = out.File + file_entity_stream_message_proto_rawDesc = nil + file_entity_stream_message_proto_goTypes = nil + file_entity_stream_message_proto_depIdxs = nil +} diff --git a/grpc/stream_message_code.pb.go b/grpc/stream_message_code.pb.go new file mode 100644 index 00000000..90e1f9f6 --- /dev/null +++ b/grpc/stream_message_code.pb.go @@ -0,0 +1,190 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/stream_message_code.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type StreamMessageCode int32 + +const ( + // ping worker nodes to check their health + StreamMessageCode_PING StreamMessageCode = 0 + // ask worker node(s) to run task with given id + StreamMessageCode_RUN_TASK StreamMessageCode = 1 + // ask worker node(s) to cancel task with given id + StreamMessageCode_CANCEL_TASK StreamMessageCode = 2 + // insert data + StreamMessageCode_INSERT_DATA StreamMessageCode = 3 + // insert logs + StreamMessageCode_INSERT_LOGS StreamMessageCode = 4 + // send event + StreamMessageCode_SEND_EVENT StreamMessageCode = 5 + // install plugin + StreamMessageCode_INSTALL_PLUGIN StreamMessageCode = 6 + // uninstall plugin + StreamMessageCode_UNINSTALL_PLUGIN StreamMessageCode = 7 + // start plugin + StreamMessageCode_START_PLUGIN StreamMessageCode = 8 + // stop plugin + StreamMessageCode_STOP_PLUGIN StreamMessageCode = 9 + // connect + StreamMessageCode_CONNECT StreamMessageCode = 10 + // disconnect + StreamMessageCode_DISCONNECT StreamMessageCode = 11 + // send + StreamMessageCode_SEND StreamMessageCode = 12 +) + +// Enum value maps for StreamMessageCode. +var ( + StreamMessageCode_name = map[int32]string{ + 0: "PING", + 1: "RUN_TASK", + 2: "CANCEL_TASK", + 3: "INSERT_DATA", + 4: "INSERT_LOGS", + 5: "SEND_EVENT", + 6: "INSTALL_PLUGIN", + 7: "UNINSTALL_PLUGIN", + 8: "START_PLUGIN", + 9: "STOP_PLUGIN", + 10: "CONNECT", + 11: "DISCONNECT", + 12: "SEND", + } + StreamMessageCode_value = map[string]int32{ + "PING": 0, + "RUN_TASK": 1, + "CANCEL_TASK": 2, + "INSERT_DATA": 3, + "INSERT_LOGS": 4, + "SEND_EVENT": 5, + "INSTALL_PLUGIN": 6, + "UNINSTALL_PLUGIN": 7, + "START_PLUGIN": 8, + "STOP_PLUGIN": 9, + "CONNECT": 10, + "DISCONNECT": 11, + "SEND": 12, + } +) + +func (x StreamMessageCode) Enum() *StreamMessageCode { + p := new(StreamMessageCode) + *p = x + return p +} + +func (x StreamMessageCode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (StreamMessageCode) Descriptor() protoreflect.EnumDescriptor { + return file_entity_stream_message_code_proto_enumTypes[0].Descriptor() +} + +func (StreamMessageCode) Type() protoreflect.EnumType { + return &file_entity_stream_message_code_proto_enumTypes[0] +} + +func (x StreamMessageCode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use StreamMessageCode.Descriptor instead. +func (StreamMessageCode) EnumDescriptor() ([]byte, []int) { + return file_entity_stream_message_code_proto_rawDescGZIP(), []int{0} +} + +var File_entity_stream_message_code_proto protoreflect.FileDescriptor + +var file_entity_stream_message_code_proto_rawDesc = []byte{ + 0x0a, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x2a, 0xe2, 0x01, 0x0a, 0x11, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x08, + 0x0a, 0x04, 0x50, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x55, 0x4e, 0x5f, + 0x54, 0x41, 0x53, 0x4b, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x41, 0x4e, 0x43, 0x45, 0x4c, + 0x5f, 0x54, 0x41, 0x53, 0x4b, 0x10, 0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x53, 0x45, 0x52, + 0x54, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x10, 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x49, 0x4e, 0x53, 0x45, + 0x52, 0x54, 0x5f, 0x4c, 0x4f, 0x47, 0x53, 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x45, 0x4e, + 0x44, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x10, 0x05, 0x12, 0x12, 0x0a, 0x0e, 0x49, 0x4e, 0x53, + 0x54, 0x41, 0x4c, 0x4c, 0x5f, 0x50, 0x4c, 0x55, 0x47, 0x49, 0x4e, 0x10, 0x06, 0x12, 0x14, 0x0a, + 0x10, 0x55, 0x4e, 0x49, 0x4e, 0x53, 0x54, 0x41, 0x4c, 0x4c, 0x5f, 0x50, 0x4c, 0x55, 0x47, 0x49, + 0x4e, 0x10, 0x07, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x41, 0x52, 0x54, 0x5f, 0x50, 0x4c, 0x55, + 0x47, 0x49, 0x4e, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x54, 0x4f, 0x50, 0x5f, 0x50, 0x4c, + 0x55, 0x47, 0x49, 0x4e, 0x10, 0x09, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x10, 0x0a, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, + 0x54, 0x10, 0x0b, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x45, 0x4e, 0x44, 0x10, 0x0c, 0x42, 0x08, 0x5a, + 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_stream_message_code_proto_rawDescOnce sync.Once + file_entity_stream_message_code_proto_rawDescData = file_entity_stream_message_code_proto_rawDesc +) + +func file_entity_stream_message_code_proto_rawDescGZIP() []byte { + file_entity_stream_message_code_proto_rawDescOnce.Do(func() { + file_entity_stream_message_code_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_stream_message_code_proto_rawDescData) + }) + return file_entity_stream_message_code_proto_rawDescData +} + +var file_entity_stream_message_code_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_entity_stream_message_code_proto_goTypes = []interface{}{ + (StreamMessageCode)(0), // 0: grpc.StreamMessageCode +} +var file_entity_stream_message_code_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_stream_message_code_proto_init() } +func file_entity_stream_message_code_proto_init() { + if File_entity_stream_message_code_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_stream_message_code_proto_rawDesc, + NumEnums: 1, + NumMessages: 0, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_stream_message_code_proto_goTypes, + DependencyIndexes: file_entity_stream_message_code_proto_depIdxs, + EnumInfos: file_entity_stream_message_code_proto_enumTypes, + }.Build() + File_entity_stream_message_code_proto = out.File + file_entity_stream_message_code_proto_rawDesc = nil + file_entity_stream_message_code_proto_goTypes = nil + file_entity_stream_message_code_proto_depIdxs = nil +} diff --git a/grpc/stream_message_data_task.pb.go b/grpc/stream_message_data_task.pb.go new file mode 100644 index 00000000..e2ffac6e --- /dev/null +++ b/grpc/stream_message_data_task.pb.go @@ -0,0 +1,158 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: entity/stream_message_data_task.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type StreamMessageDataTask struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` +} + +func (x *StreamMessageDataTask) Reset() { + *x = StreamMessageDataTask{} + if protoimpl.UnsafeEnabled { + mi := &file_entity_stream_message_data_task_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StreamMessageDataTask) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StreamMessageDataTask) ProtoMessage() {} + +func (x *StreamMessageDataTask) ProtoReflect() protoreflect.Message { + mi := &file_entity_stream_message_data_task_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StreamMessageDataTask.ProtoReflect.Descriptor instead. +func (*StreamMessageDataTask) Descriptor() ([]byte, []int) { + return file_entity_stream_message_data_task_proto_rawDescGZIP(), []int{0} +} + +func (x *StreamMessageDataTask) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *StreamMessageDataTask) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +var File_entity_stream_message_data_task_proto protoreflect.FileDescriptor + +var file_entity_stream_message_data_task_proto_rawDesc = []byte{ + 0x0a, 0x25, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x74, 0x61, 0x73, + 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0x44, 0x0a, + 0x15, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x44, 0x61, + 0x74, 0x61, 0x54, 0x61, 0x73, 0x6b, 0x12, 0x17, 0x0a, 0x07, 0x74, 0x61, 0x73, 0x6b, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61, 0x73, 0x6b, 0x49, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_entity_stream_message_data_task_proto_rawDescOnce sync.Once + file_entity_stream_message_data_task_proto_rawDescData = file_entity_stream_message_data_task_proto_rawDesc +) + +func file_entity_stream_message_data_task_proto_rawDescGZIP() []byte { + file_entity_stream_message_data_task_proto_rawDescOnce.Do(func() { + file_entity_stream_message_data_task_proto_rawDescData = protoimpl.X.CompressGZIP(file_entity_stream_message_data_task_proto_rawDescData) + }) + return file_entity_stream_message_data_task_proto_rawDescData +} + +var file_entity_stream_message_data_task_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_entity_stream_message_data_task_proto_goTypes = []interface{}{ + (*StreamMessageDataTask)(nil), // 0: grpc.StreamMessageDataTask +} +var file_entity_stream_message_data_task_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_entity_stream_message_data_task_proto_init() } +func file_entity_stream_message_data_task_proto_init() { + if File_entity_stream_message_data_task_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_entity_stream_message_data_task_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StreamMessageDataTask); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_entity_stream_message_data_task_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_entity_stream_message_data_task_proto_goTypes, + DependencyIndexes: file_entity_stream_message_data_task_proto_depIdxs, + MessageInfos: file_entity_stream_message_data_task_proto_msgTypes, + }.Build() + File_entity_stream_message_data_task_proto = out.File + file_entity_stream_message_data_task_proto_rawDesc = nil + file_entity_stream_message_data_task_proto_goTypes = nil + file_entity_stream_message_data_task_proto_depIdxs = nil +} diff --git a/grpc/task.pb.go b/grpc/task.pb.go new file mode 100644 index 00000000..af5aa474 --- /dev/null +++ b/grpc/task.pb.go @@ -0,0 +1,241 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: models/task.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type Task struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + XId string `protobuf:"bytes,1,opt,name=_id,json=Id,proto3" json:"_id,omitempty"` + SpiderId string `protobuf:"bytes,2,opt,name=spider_id,json=spiderId,proto3" json:"spider_id,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + NodeId string `protobuf:"bytes,6,opt,name=node_id,json=nodeId,proto3" json:"node_id,omitempty"` + Cmd string `protobuf:"bytes,8,opt,name=cmd,proto3" json:"cmd,omitempty"` + Param string `protobuf:"bytes,9,opt,name=param,proto3" json:"param,omitempty"` + Error string `protobuf:"bytes,10,opt,name=error,proto3" json:"error,omitempty"` + Pid int32 `protobuf:"varint,16,opt,name=pid,proto3" json:"pid,omitempty"` + RunType string `protobuf:"bytes,17,opt,name=run_type,json=runType,proto3" json:"run_type,omitempty"` + ScheduleId string `protobuf:"bytes,18,opt,name=schedule_id,json=scheduleId,proto3" json:"schedule_id,omitempty"` + Type string `protobuf:"bytes,19,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *Task) Reset() { + *x = Task{} + if protoimpl.UnsafeEnabled { + mi := &file_models_task_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Task) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Task) ProtoMessage() {} + +func (x *Task) ProtoReflect() protoreflect.Message { + mi := &file_models_task_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Task.ProtoReflect.Descriptor instead. +func (*Task) Descriptor() ([]byte, []int) { + return file_models_task_proto_rawDescGZIP(), []int{0} +} + +func (x *Task) GetXId() string { + if x != nil { + return x.XId + } + return "" +} + +func (x *Task) GetSpiderId() string { + if x != nil { + return x.SpiderId + } + return "" +} + +func (x *Task) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *Task) GetNodeId() string { + if x != nil { + return x.NodeId + } + return "" +} + +func (x *Task) GetCmd() string { + if x != nil { + return x.Cmd + } + return "" +} + +func (x *Task) GetParam() string { + if x != nil { + return x.Param + } + return "" +} + +func (x *Task) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +func (x *Task) GetPid() int32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *Task) GetRunType() string { + if x != nil { + return x.RunType + } + return "" +} + +func (x *Task) GetScheduleId() string { + if x != nil { + return x.ScheduleId + } + return "" +} + +func (x *Task) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +var File_models_task_proto protoreflect.FileDescriptor + +var file_models_task_proto_rawDesc = []byte{ + 0x0a, 0x11, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0x85, 0x02, 0x0a, 0x04, 0x54, 0x61, + 0x73, 0x6b, 0x12, 0x0f, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x70, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x6e, 0x6f, 0x64, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6e, 0x6f, 0x64, 0x65, 0x49, + 0x64, 0x12, 0x10, 0x0a, 0x03, 0x63, 0x6d, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x63, 0x6d, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, + 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x10, 0x0a, 0x03, 0x70, 0x69, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x70, 0x69, + 0x64, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x11, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, +} + +var ( + file_models_task_proto_rawDescOnce sync.Once + file_models_task_proto_rawDescData = file_models_task_proto_rawDesc +) + +func file_models_task_proto_rawDescGZIP() []byte { + file_models_task_proto_rawDescOnce.Do(func() { + file_models_task_proto_rawDescData = protoimpl.X.CompressGZIP(file_models_task_proto_rawDescData) + }) + return file_models_task_proto_rawDescData +} + +var file_models_task_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_models_task_proto_goTypes = []interface{}{ + (*Task)(nil), // 0: grpc.Task +} +var file_models_task_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_models_task_proto_init() } +func file_models_task_proto_init() { + if File_models_task_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_models_task_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Task); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_models_task_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_models_task_proto_goTypes, + DependencyIndexes: file_models_task_proto_depIdxs, + MessageInfos: file_models_task_proto_msgTypes, + }.Build() + File_models_task_proto = out.File + file_models_task_proto_rawDesc = nil + file_models_task_proto_goTypes = nil + file_models_task_proto_depIdxs = nil +} diff --git a/grpc/task_service.pb.go b/grpc/task_service.pb.go new file mode 100644 index 00000000..7ea57caa --- /dev/null +++ b/grpc/task_service.pb.go @@ -0,0 +1,95 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.20.1 +// source: services/task_service.proto + +package grpc + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +var File_services_task_service_proto protoreflect.FileDescriptor + +var file_services_task_service_proto_rawDesc = []byte{ + 0x0a, 0x1b, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x74, 0x61, 0x73, 0x6b, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, + 0x72, 0x70, 0x63, 0x1a, 0x14, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x15, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x2f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x1a, 0x1b, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0xa2, 0x01, + 0x0a, 0x0b, 0x54, 0x61, 0x73, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x34, 0x0a, + 0x09, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x12, 0x13, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, + 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x28, 0x01, 0x12, 0x28, 0x0a, 0x05, 0x46, 0x65, 0x74, 0x63, 0x68, 0x12, 0x0d, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x33, 0x0a, + 0x10, 0x53, 0x65, 0x6e, 0x64, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0d, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x0e, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x00, 0x42, 0x08, 0x5a, 0x06, 0x2e, 0x3b, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var file_services_task_service_proto_goTypes = []interface{}{ + (*StreamMessage)(nil), // 0: grpc.StreamMessage + (*Request)(nil), // 1: grpc.Request + (*Response)(nil), // 2: grpc.Response +} +var file_services_task_service_proto_depIdxs = []int32{ + 0, // 0: grpc.TaskService.Subscribe:input_type -> grpc.StreamMessage + 1, // 1: grpc.TaskService.Fetch:input_type -> grpc.Request + 1, // 2: grpc.TaskService.SendNotification:input_type -> grpc.Request + 2, // 3: grpc.TaskService.Subscribe:output_type -> grpc.Response + 2, // 4: grpc.TaskService.Fetch:output_type -> grpc.Response + 2, // 5: grpc.TaskService.SendNotification:output_type -> grpc.Response + 3, // [3:6] is the sub-list for method output_type + 0, // [0:3] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_services_task_service_proto_init() } +func file_services_task_service_proto_init() { + if File_services_task_service_proto != nil { + return + } + file_entity_request_proto_init() + file_entity_response_proto_init() + file_entity_stream_message_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_services_task_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 0, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_services_task_service_proto_goTypes, + DependencyIndexes: file_services_task_service_proto_depIdxs, + }.Build() + File_services_task_service_proto = out.File + file_services_task_service_proto_rawDesc = nil + file_services_task_service_proto_goTypes = nil + file_services_task_service_proto_depIdxs = nil +} diff --git a/grpc/task_service_grpc.pb.go b/grpc/task_service_grpc.pb.go new file mode 100644 index 00000000..6afa1226 --- /dev/null +++ b/grpc/task_service_grpc.pb.go @@ -0,0 +1,212 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.20.1 +// source: services/task_service.proto + +package grpc + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// TaskServiceClient is the client API for TaskService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TaskServiceClient interface { + Subscribe(ctx context.Context, opts ...grpc.CallOption) (TaskService_SubscribeClient, error) + Fetch(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) + SendNotification(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) +} + +type taskServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTaskServiceClient(cc grpc.ClientConnInterface) TaskServiceClient { + return &taskServiceClient{cc} +} + +func (c *taskServiceClient) Subscribe(ctx context.Context, opts ...grpc.CallOption) (TaskService_SubscribeClient, error) { + stream, err := c.cc.NewStream(ctx, &TaskService_ServiceDesc.Streams[0], "/grpc.TaskService/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &taskServiceSubscribeClient{stream} + return x, nil +} + +type TaskService_SubscribeClient interface { + Send(*StreamMessage) error + CloseAndRecv() (*Response, error) + grpc.ClientStream +} + +type taskServiceSubscribeClient struct { + grpc.ClientStream +} + +func (x *taskServiceSubscribeClient) Send(m *StreamMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *taskServiceSubscribeClient) CloseAndRecv() (*Response, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(Response) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *taskServiceClient) Fetch(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.TaskService/Fetch", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskServiceClient) SendNotification(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) { + out := new(Response) + err := c.cc.Invoke(ctx, "/grpc.TaskService/SendNotification", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TaskServiceServer is the server API for TaskService service. +// All implementations must embed UnimplementedTaskServiceServer +// for forward compatibility +type TaskServiceServer interface { + Subscribe(TaskService_SubscribeServer) error + Fetch(context.Context, *Request) (*Response, error) + SendNotification(context.Context, *Request) (*Response, error) + mustEmbedUnimplementedTaskServiceServer() +} + +// UnimplementedTaskServiceServer must be embedded to have forward compatible implementations. +type UnimplementedTaskServiceServer struct { +} + +func (UnimplementedTaskServiceServer) Subscribe(TaskService_SubscribeServer) error { + return status.Errorf(codes.Unimplemented, "method Subscribe not implemented") +} +func (UnimplementedTaskServiceServer) Fetch(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method Fetch not implemented") +} +func (UnimplementedTaskServiceServer) SendNotification(context.Context, *Request) (*Response, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendNotification not implemented") +} +func (UnimplementedTaskServiceServer) mustEmbedUnimplementedTaskServiceServer() {} + +// UnsafeTaskServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TaskServiceServer will +// result in compilation errors. +type UnsafeTaskServiceServer interface { + mustEmbedUnimplementedTaskServiceServer() +} + +func RegisterTaskServiceServer(s grpc.ServiceRegistrar, srv TaskServiceServer) { + s.RegisterService(&TaskService_ServiceDesc, srv) +} + +func _TaskService_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(TaskServiceServer).Subscribe(&taskServiceSubscribeServer{stream}) +} + +type TaskService_SubscribeServer interface { + SendAndClose(*Response) error + Recv() (*StreamMessage, error) + grpc.ServerStream +} + +type taskServiceSubscribeServer struct { + grpc.ServerStream +} + +func (x *taskServiceSubscribeServer) SendAndClose(m *Response) error { + return x.ServerStream.SendMsg(m) +} + +func (x *taskServiceSubscribeServer) Recv() (*StreamMessage, error) { + m := new(StreamMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _TaskService_Fetch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServiceServer).Fetch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.TaskService/Fetch", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServiceServer).Fetch(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskService_SendNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Request) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServiceServer).SendNotification(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/grpc.TaskService/SendNotification", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServiceServer).SendNotification(ctx, req.(*Request)) + } + return interceptor(ctx, in, info, handler) +} + +// TaskService_ServiceDesc is the grpc.ServiceDesc for TaskService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TaskService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "grpc.TaskService", + HandlerType: (*TaskServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Fetch", + Handler: _TaskService_Fetch_Handler, + }, + { + MethodName: "SendNotification", + Handler: _TaskService_SendNotification_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Subscribe", + Handler: _TaskService_Subscribe_Handler, + ClientStreams: true, + }, + }, + Metadata: "services/task_service.proto", +}